Master form validation in React using React Hook Form and Zod schema validation for type-safe, performant forms.

Abdur Razzak
Full-Stack Web Developer
React Hook Form is the most performant form library for React — it uses uncontrolled components to minimize re-renders. Paired with Zod, a TypeScript-first schema validation library, you get end-to-end type safety from your schema definition to your form state. Together they eliminate the boilerplate of manual validation logic and keep your code clean and maintainable.
Install the packages: npm install react-hook-form zod @hookform/resolvers. The @hookform/resolvers package bridges Zod schemas with React Hook Form. Define your validation schema using z.object() with Zod validators like z.string().min(1), z.email(), z.number().positive(). Pass the schema to the zodResolver() function and provide it as the resolver option to useForm.
Use useForm with your Zod schema to get register, handleSubmit, and formState.errors. Register each input with the register() function and display errors from formState.errors. The handleSubmit wrapper prevents form submission when validation fails and types the data parameter according to your Zod schema automatically.
Zod supports complex validation patterns: z.string().regex() for patterns, z.enum() for allowed values, z.union() for multiple valid types, and z.discriminatedUnion() for conditional schemas. Use .refine() for custom validation logic, such as confirming a password matches its confirmation field. Zod transforms with .transform() let you coerce string inputs to numbers or dates.
After form submission, server validation errors (e.g., 'email already in use') need to be shown to the user. React Hook Form provides setError() to programmatically set errors on specific fields. Call setError('email', { message: 'This email is already registered' }) in your catch block after a failed API call. This keeps the UX consistent between client and server validation.
React Hook Form avoids unnecessary re-renders by using refs instead of state for most operations. Use the Controller component for third-party inputs like custom date pickers or select dropdowns that don't expose a ref. Keep Zod schemas in separate files and share them between frontend and backend for consistent validation. For large forms, use useWatch selectively to read specific field values without re-rendering the whole form.