0.4.0 • Published 1 month ago

react-zod-form v0.4.0

Weekly downloads
-
License
MIT
Repository
github
Last release
1 month ago

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@example.com"),
  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@example.com"),
  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@example.com"),
  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@example.com"),
  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@example.com"),
  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@example.com"),
  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@example.com"),
  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

TypeOutputDescription
anynumberIf value is a parsable number, it will be converted to number.
anybooleanIf value is "true" or "false", it will be converted to boolean.
"radio" | "checkbox"booleanIf 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.
0.4.0-rc3

1 month ago

0.4.0-rc2

1 month ago

0.4.0-rc1

1 month ago

0.4.0

1 month ago

0.4.0-rc0

1 month ago

0.3.2

5 months ago

0.3.0

5 months ago

0.3.1

5 months ago

0.2.11

5 months ago

0.2.10

5 months ago

0.2.7

6 months ago

0.2.6

6 months ago

0.2.9

5 months ago

0.2.8

6 months ago

0.2.3

6 months ago

0.2.5

6 months ago

0.2.4

6 months ago

0.2.1

11 months ago

0.2.0

11 months ago

0.2.2

11 months ago

0.1.1

1 year ago

0.1.0

1 year ago

0.0.0

1 year ago