React Hook Form
React Hook Form
◆ Definition
React Hook Form (RHF): A performant, minimal-re-render form library that uses uncontrolled inputs under the hood (via refs) while exposing a clean hook-based API useForm for registration, validation, and submission. It is the de-facto standard in modern React projects.
React Hook Form causes zero re-renders on keystrokes, has built-in validation, integrates with Zod and Yup schema validators, and handles complex scenarios like multi-step forms and field arrays with minimal code.
Installation
Terminal
npm install react-hook-form
# Optional: Zod for schema-based validation
npm install @hookform/resolvers zod
Basic Usage with Built-in Validation
JSX — RHFBasic.jsx
import { useForm } from 'react-hook-form'; function RHFBasic() { const { register, handleSubmit, formState: { errors, isSubmitting }, reset, } = useForm(); const onSubmit = async (data) => { // data = { email: '...', password: '...' } await fakeApiCall(data); reset(); // clear form after success }; return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('email', { required: 'Email is required', pattern: { value: /^\S+@\S+\.\S+$/i, message: 'Enter a valid email address', }, })} type="email" placeholder="Email" /> {errors.email && <span style={{ color: 'red', fontSize: '13px' }}> {errors.email.message} </span>} <input {...register('password', { required: 'Password is required', minLength: { value: 8, message: 'Minimum 8 characters' }, })} type="password" placeholder="Password" /> {errors.password && <span style={{ color: 'red', fontSize: '13px' }}> {errors.password.message} </span>} <button type="submit" disabled={isSubmitting}> {isSubmitting ? 'Submitting...' : 'Submit'} </button> </form> ); }
RHF + Zod Schema Validation (Production Pattern)
JSX — ZodForm.jsx
import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; // 1. Define schema — can be shared with backend (tRPC, Next.js) const schema = z.object({ name: z.string().min(2, 'Name must be at least 2 characters'), email: z.string().email('Enter a valid email address'), age: z.number().min(18, 'You must be 18 or older'), website: z.string().url().optional().or(z.literal('')), }); // Infer TypeScript type from schema // type FormValues = z.infer<typeof schema> function ZodForm() { const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(schema), // ← plug Zod into RHF defaultValues: { name: '', email: '', age: 18, website: '' }, }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('name')} placeholder="Full name" /> {errors.name && <p>{errors.name.message}</p>} <input {...register('email')} placeholder="Email" /> {errors.email && <p>{errors.email.message}</p>} <input {...register('age', { valueAsNumber: true })} type="number" placeholder="Age" /> {errors.age && <p>{errors.age.message}</p>} <button type="submit">Save Profile</button> </form> ); } export default ZodForm;
Prefer React Hook Form + Zod in production. Zod schemas can be shared between the frontend and backend (e.g., in a tRPC or Next.js project), giving you fully type-safe validation end-to-end one schema, zero duplication.
