React Form Validation — Working Code (React Hook Form + Zod)
Modern React form validation uses React Hook Form for state management and Zod for typed schemas. Working code with inline errors, password confirmation, async submission to splitforms.
The modern React form validation stack is React Hook Form (state + form mechanics) plus Zod (typed schemas + validation rules). The combination gives you type-safe form data, inline error rendering, async submission, and minimal re-renders — all in ~20 lines per form.
Define your validation rules as a Zod schema. Each field gets a type and constraints — `z.string().min(2)`, `z.string().email()`, `z.number().int().positive()`. The `.refine()` method handles cross-field rules like password confirmation. The schema is the single source of truth for both validation logic and TypeScript types via `z.infer<typeof schema>`.
React Hook Form's `useForm` hook gives you `register` (wires inputs into the form state), `handleSubmit` (runs validation, calls your submit function), and `formState.errors` (the validation errors object). Pass `resolver: zodResolver(schema)` to wire Zod in. That's the whole integration.
Submit handler is a regular async function that receives the validated, typed `data` object. Make a fetch call to splitforms's `/api/submit` endpoint, await the response, update UI accordingly. The `isSubmitting` boolean from formState handles the disable-during-submit pattern automatically.
How to set this up
Install dependencies
npm install react-hook-form @hookform/resolvers zod — three packages, ~25KB minified combined.
Define a Zod schema
One schema per form. Each field has a type + constraints. .refine() for cross-field rules. .infer<typeof schema> gives you the TypeScript type.
Wire useForm with zodResolver
useForm({ resolver: zodResolver(schema) }) connects RHF state management to your Zod validation.
Render and submit
register('field') on each input, errors.field?.message for inline error display, handleSubmit(onSubmit) wires the submit.
20 lines per form, fully typed, ~25KB total runtime.
Frequently asked questions
What's the best React form validation library?
React Hook Form + Zod is the 2026 default for new React projects. RHF handles state and re-renders efficiently; Zod handles validation rules and type generation. Together they're typed, performant, and ~25KB combined.
How do I validate a form in React?
Use useForm from react-hook-form with a zodResolver. Define a Zod schema for your fields. Call register(fieldName) on each input. handleSubmit(onSubmit) runs validation; errors render from formState.errors.
How do I do password confirmation in React Hook Form?
Use Zod's .refine() method on the schema: `.refine((d) => d.password === d.password_confirm, { message: "Passwords don't match", path: ["password_confirm"] })`. The error attaches to the password_confirm field.
Yup vs Zod — which should I use?
Zod for new projects. It's TypeScript-first (Yup was retrofitted), produces better type inference, and has a more modern API. Yup is still fine if you're maintaining an existing codebase — both pair with React Hook Form via the @hookform/resolvers package.
How do I handle async validation (e.g., 'is email taken')?
Zod doesn't do async validation natively. Use React Hook Form's `mode: 'onBlur'` and add async validation in onSubmit, or use the `setError` API to add a server-returned validation error after the submission fails.
Does React Hook Form work with server components?
React Hook Form runs in client components only ('use client' directive required). For server-component-only validation, use Next.js Server Actions with Zod schemas — same Zod schema, different invocation path.
Related guides
Ship the form, not the backend.
Free for 1,000 submissions/month. Email delivery, AI spam filtering, signed webhooks, real dashboard — all on the free plan. No credit card.
Get a free access key →