Lädt...

🔧 Next.js form validation on the client and server with Zod


Nachrichtenbereich: 🔧 Programmierung
🔗 Quelle: dev.to

Building robust forms in Next.js is thirsty work!

Not only must you validate forms on the server, you must validate them on the client as well.

On the client, to ensure the user has a smooth experience, fields should be revalidated when their value changes, but only if the field has been "touched" or the form previously-submitted.

If JavaScript is disabled, the form ought to regress gracefully. Dynamic form validation won't be possible, but errors should still render alongside their respective fields and preserve their values between requests to the server.

You want to do all this without writing a bunch of duplicate code and, in this case, without a third-party form library like React Hook Form.

Zod allows you to define the shape of a valid for submission. Provided you do so in a separate file, you can reference the definition from either the server or a client component, eliminating the possibility of duplicate code.

import { z } from "zod"

export const signUpFormSchema = z.object({
  email: z.string().email({ message: "Please enter a valid email." }).trim(),
  password: z
    .string()
    .min(8, { message: "Be at least 8 characters long" })
    .regex(/[a-zA-Z]/, { message: "Contain at least one letter." })
    .regex(/[0-9]/, { message: "Contain at least one number." })
    .regex(/[^a-zA-Z0-9]/, {
      message: "Contain at least one special character."
    })
    .trim()
})

export type SignUpActionState = {
  form?: {
    email?: string
    password?: string
  }
  errors?: {
    email?: string[]
    password?: string[]
  }
}

To validate the form on the server, import and and validate against the schema when the sever action is submitted:

"use server"

import { redirect } from "next/navigation"
import { SignUpActionState, signUpFormSchema } from "./schema"

export async function signUpAction(
  _prev: SignUpActionState,
  formData: FormData
): Promise<SignUpActionState> {
  const form = Object.fromEntries(formData)
  const validationResult = signUpFormSchema.safeParse(form)
  if (!validationResult.success) {
    return {
      form,
      errors: validationResult.error.flatten().fieldErrors
    }
  }

  redirect("/")
}

On the client, in a client component denoted with "use client", create your form.

ℹ️ Info
<ValidatedInput /> isn't defined yet; take a moment to understand the form first.
"use client"

import { useActionState, useState } from "react"
import { signUpAction } from "./action"
import { signUpFormSchema } from "./schema"
import { ValidatedInput } from "@/components/ui/validated-input"

export default function SignUpForm() {
  const [wasSubmitted, setWasSubmitted] = useState(false)

  const [state, action, isPending] = useActionState(signUpAction, {})

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    setWasSubmitted(true)
    const formData = new FormData(event.currentTarget)
    const data = Object.fromEntries(formData)
    const validationResult = signUpFormSchema.safeParse(data)
    if (!validationResult.success) {
      event.preventDefault()
    }
  }

  return (
    <form onSubmit={handleSubmit} action={action} noValidate>
      <div>
        <label htmlFor="email">Email:</label>
        <ValidatedInput
          type="email"
          name="email"
          wasSubmitted={wasSubmitted}
          fieldSchema={signUpFormSchema.shape["email"]}
          defaultValue={state.form?.email}
          errors={state.errors?.email}
        />
      </div>
      <div>
        <label htmlFor="password">Password:</label>
        <ValidatedInput
          type="password"
          name="password"
          fieldSchema={signUpFormSchema.shape["password"]}
          wasSubmitted={wasSubmitted}
          defaultValue={state.form?.password}
          errors={state.errors?.password}
        />
      </div>
      <div>
        <button type="submit" disabled={isPending}>
          Continue
        </button>
      </div>
    </form>
  )
}

When the form is submitted, onSubmit validates the form before indirectly invoking the server action.

The form is not concerned with rendering errors; that is the responsibility of <ValidatedInput />:

<ValidatedInput
  type="password"
  name="password"
  fieldSchema={signUpFormSchema.shape["password"]}
  wasSubmitted={wasSubmitted}
  defaultValue={state.form?.password}
  errors={state.errors?.password}
/>

Note how we extract the fieldSchema from signUpFormSchema using signUpFormSchema.shape. By passing the field schema in this way, <ValidatedInput /> remains flexible and reusable across different forms.

Here's <ValiatedInput /> in full:

import { useState, useCallback } from "react"
import { Input } from "./input"

const ValidatedInput = ({
  name,
  wasSubmitted,
  errors,
  fieldSchema,
  ...props
}) => {
  const [value, setValue] = useState("")
  const [touched, setTouched] = useState(false)

  const getErrors = useCallback(() => {
    const validationResult = fieldSchema.safeParse(value)
    return validationResult.success
      ? []
      : validationResult.error.flatten().formErrors
  }, [fieldSchema, value])

  const fieldErrors = errors || getErrors()
  const shouldRenderErrors = errors || wasSubmitted || touched

  const handleBlur = () => setTouched(true)
  const handleChange = (e) => setValue(e.currentTarget.value)

  return (
    <>
      <Input
        id={name}
        name={name}
        onBlur={handleBlur}
        onChange={handleChange}
        className={fieldErrors.length > 0 ? "border-red-500" : ""}
        {...props}
      />
      {shouldRenderErrors && (
        <span className="text-sm text-red-500">{fieldErrors}</span>
      )}
    </>
  )
}
export { ValidatedInput }
...

🔧 Next.js form validation on the client and server with Zod


📈 50.63 Punkte
🔧 Programmierung

🔧 Next.js form validation on the client and server with Zod


📈 50.63 Punkte
🔧 Programmierung

🔧 Form Validation In TypeScipt Projects Using Zod and React Hook Form


📈 43.89 Punkte
🔧 Programmierung

🔧 Simplifying Form Validation with Zod and React Hook Form


📈 43.89 Punkte
🔧 Programmierung

🔧 Next JS Form Validation Zod Example - NextJS Tutorial


📈 39.45 Punkte
🔧 Programmierung

🔧 Introducing the Zod Schema Designer: A Visual Tool for Creating and Editing Zod Schemas


📈 39.32 Punkte
🔧 Programmierung

🔧 Building a reusable multi-step form with React Hook Form and Zod


📈 37.19 Punkte
🔧 Programmierung

🔧 Why React Hook Form and Zod are Essential to Build Contact Form


📈 37.19 Punkte
🔧 Programmierung

🔧 How to Handle Forms in Next.js with Server Actions and Zod for Validation


📈 35.54 Punkte
🔧 Programmierung

🔧 Both-sides form validation with Next.js (React Hook Form &amp; next-safe-action)


📈 34.24 Punkte
🔧 Programmierung

🔧 How to Create Dynamic Email Contact Form in Next.js Using Resend and Zod


📈 34.11 Punkte
🔧 Programmierung

🔧 Form Validation in Remix with Zod 🔐


📈 34.1 Punkte
🔧 Programmierung

🔧 Form Validation in Remix with Zod 🔐


📈 34.1 Punkte
🔧 Programmierung

🔧 Leveraging Zod for Form Validation in React: Unleashing the Power of superRefine


📈 34.1 Punkte
🔧 Programmierung

🔧 API Validation in Next.js 15 with Zod and TypeScript


📈 32.37 Punkte
🔧 Programmierung

🔧 Building CRUD App with react-form, zod, react data grid, react-query and json-server


📈 31.93 Punkte
🔧 Programmierung

🔧 12 Lựa Chọn Thay Thế Vercel Cần Xem Xét Vào Năm 2025


📈 29.19 Punkte
🔧 Programmierung

🔧 Grok 3: AI Thông Minh Nhất Thế Giới


📈 29.19 Punkte
🔧 Programmierung

🕵️ Kèo Thẻ Phạt Vip66 Là Gì? 3 Lối Đánh Kèo Chậm Mà Chắc


📈 29.19 Punkte
🕵️ Reverse Engineering

🔧 KISS Principle: Giữ Mọi Thứ Đơn Giản Nhất Có Thể


📈 29.19 Punkte
🔧 Programmierung

🔧 Có thể bạn chưa biết (Phần 1)


📈 29.19 Punkte
🔧 Programmierung

🔧 A Simple Documentation for using React Hook Form and Zod


📈 28.76 Punkte
🔧 Programmierung

🔧 How to create a contact form with EmailJS, Zod, ShadCN UI, TypeScript using React and NextJS.


📈 28.76 Punkte
🔧 Programmierung

🔧 Creating Dynamic Forms with React, Typescript, React Hook Form and Zod


📈 28.76 Punkte
🔧 Programmierung

🔧 How to Validate Forms with Zod and React-Hook-Form


📈 28.76 Punkte
🔧 Programmierung

🔧 React and express.js form validations with Zod


📈 28.76 Punkte
🔧 Programmierung

🔧 Building React Forms with Ease Using React Hook Form, Zod and Shadcn


📈 28.76 Punkte
🔧 Programmierung

🔧 Form - Next15/React19/ReactHookForm + Zod + useActionState


📈 27.41 Punkte
🔧 Programmierung

🔧 Formulários com React Hook Form + Zod


📈 27.41 Punkte
🔧 Programmierung