React Form Validation (React Hook Form + Zod)
Type-safe forms, custom validators, error display, and submission handling
What You’ll Build
After following this guide, you will have a working implementation of react form validation (react hook form + zod) in your project. Build type-safe, performant forms in React using React Hook Form + Zod. React Hook Form uses uncontrolled inputs for minimal re-renders, and Zod provides schema validation with automatic TypeScript type inference. No boilerplate, no performance issues.
Use Cases & Problems Solved
- Implement interactive UI features that users expect from modern apps
- Follow established patterns that scale and remain maintainable
- Reduce boilerplate and avoid common frontend pitfalls
Prerequisites
- React 18+
- TypeScript (recommended)
Step-by-Step Implementation
Install dependencies
The following snippet shows how to install dependencies. Copy this into your project and adjust the values for your environment.
npm install react-hook-form zod @hookform/resolvers
Define schema and form
The following snippet shows how to define schema and form. Copy this into your project and adjust the values for your environment.
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Must be at least 8 characters'),
confirmPassword: z.string(),
}).refine(data => data.password === data.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword'],
});
type FormData = z.infer<typeof schema>;
export default function SignUpForm() {
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>({
resolver: zodResolver(schema),
});
const onSubmit = async (data: FormData) => {
await fetch('/api/signup', { method: 'POST', body: JSON.stringify(data) });
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} placeholder="Email" />
{errors.email && <span className="error">{errors.email.message}</span>}
<input {...register('password')} type="password" placeholder="Password" />
{errors.password && <span className="error">{errors.password.message}</span>}
<input {...register('confirmPassword')} type="password" placeholder="Confirm" />
{errors.confirmPassword && <span className="error">{errors.confirmPassword.message}</span>}
<button disabled={isSubmitting}>{isSubmitting ? 'Signing up...' : 'Sign Up'}</button>
</form>
);
}
⚠️ Don’t Do This
❌ Using controlled inputs with useState for every field
// Re-renders entire form on EVERY keystroke!
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
<input value={email} onChange={e => setEmail(e.target.value)} />
✅ Use uncontrolled inputs with register (React Hook Form)
// Only re-renders on submit or validation — much faster!
const { register } = useForm();
<input {...register('email')} /> // No useState needed
Testing
Verify your implementation with these tests:
// __tests__/react-form-validation-react-hook-form-zod-.test.ts
import { describe, it, expect } from 'vitest';
describe('React Form Validation (React Hook Form + Zod)', () => {
it('should initialize without errors', () => {
// Test that the setup completes successfully
expect(() => setup()).not.toThrow();
});
it('should handle the primary use case', async () => {
const result = await execute();
expect(result).toBeDefined();
expect(result.success).toBe(true);
});
it('should handle edge cases', async () => {
// Test with empty/null input
const result = await execute(null);
expect(result.error).toBeDefined();
});
});
Verification
npm start
# Fill form with invalid email → error shows inline
# Type mismatching passwords → 'do not match' error
# Submit valid form → network tab shows POST request
# Check isSubmitting disables button during submission Related Specs
Next.js Server Actions Guide
Form mutations, revalidation, optimistic updates, and error handling
Toast Notification System
Custom toast component, queue management, animations, and accessibility