npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-zod-form

v0.4.1

Published

Simple form validation and values transformation.

Downloads

542

Readme

React Zod Form

Simple form handling with full validation control.

Navigation

Other approaches

Read this article to know how to choose your form library.

Install

npm

npm i react-zod-form

Features

  • Zod
  • Field value parser by apparent rules
  • Field names helper

Usage

Getting values

If you're not familiar with Zod, begin with it first.

You start from creating new ZodForm

import ZodForm from "react-zod-form"

const form = new ZodForm()

Then you declare some fields

import ZodForm from "react-zod-form"
import { z } from "zod"

const form = new ZodForm({
  userName: z.string().min(3, "Enter at least 3 chars"),
  email: z.string().email("Follow this email format: [email protected]"),
  website: z.string().url("Follow this URL format: https://example.com")
})

You just created Zod form!

Notice: It's better to keep zod form in the low level to make sure you're creating ZodForm only once.


Now let's create react form component

function ExampleForm() {
  return (
    <form>
      <input placeholder="Enter your username" required />
      <input placeholder="Enter your email" type="email" required />
      <input placeholder="Enter your website" type="url" required />
    </form>
  )
}

export default ExampleForm

Combine zod schema and give fields their names (help yourself with fields)

import ZodForm from "react-zod-form"
import { z } from "zod"

const form = new ZodForm({
  userName: z.string().min(3, "Enter at least 3 chars"),
  email: z.string().email("Follow this email format: [email protected]"),
  website: z.string().url("Follow this URL format: https://example.com")
})

function ExampleForm() {
  return (
    <form>
      <input placeholder="Enter your username" required name={form.fields.username} />
      <input placeholder="Enter your email" type="email" required name={form.fields.email} />
      <input placeholder="Enter your website" type="url" required name={form.fields.url} />
    </form>
  )
}

export default ExampleForm

Now let's get some values on event (i.e. onBlur, onFocus, onChange, onSubmit, ...)

import { FormEvent } from "react"
import ZodForm from "react-zod-form"
import { z } from "zod"

const form = new ZodForm({
  userName: z.string().min(3, "Enter at least 3 chars"),
  email: z.string().email("Follow this email format: [email protected]"),
  website: z.string().url("Follow this URL format: https://example.com")
})

function ExampleForm() {
  /**
   * Triggered on input unfocus.
   */
  function onBlur(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()

    // Tries to return a field that was currently unfocused, otherwise throws error
    const field = form.parseCurrentField(event)
    console.log(field.name, field.value)
    
    // Tries to return all field values, otherwise throws error
    const fields = form.parseAllFields(event)
    console.log(fields)
  }
  
  return (
    <form onBlur={onBlur}>
      <input placeholder="Enter your username" required name={form.fields.username} />
      <input placeholder="Enter your email" type="email" required name={form.fields.email} />
      <input placeholder="Enter your website" type="url" required name={form.fields.url} />
    </form>
  )
}

export default ExampleForm

Wow, now you have your fields just in a few lines of code and it's all concise!

Notice: There is a safe version of parseAllFields - safeParseAllFields, works just same as in zod.

Let's talk about form interface as you may want your form to be a "standonle module"

import { FormEvent } from "react"
import ZodForm from "react-zod-form"
import { z } from "zod"

const form = new ZodForm({
  userName: z.string().min(3, "Enter at least 3 chars"),
  email: z.string().email("Follow this email format: [email protected]"),
  website: z.string().url("Follow this URL format: https://example.com")
})

export type ExampleFormFields = z.infer<typeof form.object>

interface ExampleFormProps {
  onBlur?(value: ExampleFormFields): void
}

function ExampleForm(props: ExampleFormProps) {
  /**
   * Triggered on input unfocus.
   */
  function onBlur(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()

    // Tries to return a field that was currently unfocused, otherwise throws error
    const field = form.parseCurrentField(event)
    console.log(field.name, field.value)
    
    // Tries to return all field values, otherwise throws error
    const fields = form.parseAllFields(event)
    console.log(fields)
  }
  
  return (
    <form onBlur={onBlur}>
      <input placeholder="Enter your username" required name={form.fields.username} />
      <input placeholder="Enter your email" type="email" required name={form.fields.email} />
      <input placeholder="Enter your website" type="url" required name={form.fields.url} />
    </form>
  )
}

export default ExampleForm

I'll explain you this a bit:

Interface ExampleFormFields represents a value that your output will give, your output is to be methods like onBlur, onFocus, onChange, onSubmit.

Handling issues

Issues are form errors. This is a separate module, so you need to import this along with ZodForm.

Let's take the previous example and start with reporting and clearing error

import { FormEvent } from "react"
import ZodForm, { useZodFormIssues } from "react-zod-form"
import { z } from "zod"

const form = new ZodForm({
  userName: z.string().min(3, "Enter at least 3 chars"),
  email: z.string().email("Follow this email format: [email protected]"),
  website: z.string().url("Follow this URL format: https://example.com")
})

export type ExampleFormFields = z.infer<typeof form.object>

interface ExampleFormProps {
  onBlur?(value: ExampleFormFields): void
}

function ExampleForm(props: ExampleFormProps) {
  const { reportError, clearError } = useZodFormIssues(form)
  
  /**
   * Triggered on input unfocus.
   */
  function onBlur(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()

    // Tries to return all field values, otherwise throws error
    const fields = form.parseAllFields(event)
    if (fields.success) {
      clearError() // You better clear error right after success check

      console.log(fields)
    } else {
      reportError(fields.error)
    }
  }
  
  return (
    <form onBlur={onBlur}>
      <input placeholder="Enter your username" required name={form.fields.username} />
      <input placeholder="Enter your email" type="email" required name={form.fields.email} />
      <input placeholder="Enter your website" type="url" required name={form.fields.url} />
    </form>
  )
}

export default ExampleForm

Now continue with displaying issue message

import { FormEvent } from "react"
import ZodForm, { useZodFormIssues } from "react-zod-form"
import { z } from "zod"

const form = new ZodForm({
  userName: z.string().min(3, "Enter at least 3 chars"),
  email: z.string().email("Follow this email format: [email protected]"),
  website: z.string().url("Follow this URL format: https://example.com")
})

export type ExampleFormFields = z.infer<typeof form.object>

interface ExampleFormProps {
  onBlur?(value: ExampleFormFields): void
}

function ExampleForm(props: ExampleFormProps) {
  const { reportError, clearError, fieldIssues } = useZodFormIssues(form)
  
  /**
   * Triggered on input unfocus.
   */
  function onBlur(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()

    // Tries to return all field values, otherwise throws error
    const fields = form.parseAllFields(event)
    if (fields.success) {
      clearError() // You better clear error right after success check

      console.log(fields)
    } else {
      reportError(fields.error)
    }
  }
  
  return (
    <form onBlur={onBlur}>
      {fieldIssues.username}
      <input placeholder="Enter your username" required name={form.fields.username} />
      {fieldIssues.email}
      <input placeholder="Enter your email" type="email" required name={form.fields.email} />
      {fieldIssues.url}
      <input placeholder="Enter your website" type="url" required name={form.fields.url} />
    </form>
  )
}

export default ExampleForm

Notice: fieldIssues will have the same keys as your form fields.


Let's say all your form fields are valid, but something went wrong on a backend

import { FormEvent } from "react"
import ZodForm, { useZodFormIssues } from "react-zod-form"
import { z } from "zod"

const form = new ZodForm({
  userName: z.string().min(3, "Enter at least 3 chars"),
  email: z.string().email("Follow this email format: [email protected]"),
  website: z.string().url("Follow this URL format: https://example.com")
})

export type ExampleFormFields = z.infer<typeof form.object>

interface ExampleFormProps {
  onBlur?(value: ExampleFormFields): void
}

function ExampleForm(props: ExampleFormProps) {
  const { reportError, clearError, fieldIssues, addIssue } = useZodFormIssues(form)
  
  /**
   * Triggered on input unfocus.
   */
  function onBlur(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()

    // Tries to return all field values, otherwise throws error
    const fields = form.parseAllFields(event)
    if (fields.success) {
      clearError() // You better clear error right after success check

      requestBackend(fields.data)
    } else {
      reportError(fields.error)
    }
  }

  /**
   * --- THIS IS EXAMPLE ---
   */
  function requestBackend(fields: ExampleFormFields) {
    const response = send(fields)
    if (!response.error) return

    response.error.fields.forEach(field => {
      addIssue([field.name], field.message)
    })
  }
  
  return (
    <form onBlur={onBlur}>
      {fieldIssues.username}
      <input placeholder="Enter your username" required name={form.fields.username} />
      {fieldIssues.email}
      <input placeholder="Enter your email" type="email" required name={form.fields.email} />
      {fieldIssues.url}
      <input placeholder="Enter your website" type="url" required name={form.fields.url} />
    </form>
  )
}

export default ExampleForm

Field value types

A form field value may be one of these type (can be in array):

  • string
  • number
  • boolean
  • File

Take a look at the interface to understand it better

type FormFieldValue = FormFieldValueBasic | FormFieldValueBasic[]
type FormFieldValueBasic = string | number | boolean | File

Transform rules

| Type | Output | Description | | :-----------------------: | :--------------: | --------------------------------------------------------------------------------------------------------- | | any | number | If value is a parsable number, it will be converted to number. | | any | boolean | If value is "true" or "false", it will be converted to boolean. | | "radio" | "checkbox" | boolean | If value is "ok" and type is "radio" \| "checkbox", the value from checked attribute will be taken. | | "file" | File \| File[] | If type is "file", it will give File or File[], depending on multiple attribute. |