React Hook Form is a modern, performant library for building forms in React with minimal re-renders and maximum flexibility. Here’s everything you need to know:
Core Concepts
1. Basic Usage
import { useForm } from 'react-hook-form';
function BasicForm() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register("firstName", { required: true })}
placeholder="First name"
/>
{errors.firstName && <p>First name is required</p>}
<input
{...register("lastName", { required: "Last name is required" })}
placeholder="Last name"
/>
{errors.lastName && <p>{errors.lastName.message}</p>}
<button type="submit">Submit</button>
</form>
);
}
2. Validation Schema (Yup Integration)
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
const schema = yup.object().shape({
email: yup.string().email().required(),
age: yup.number().positive().integer().required(),
});
function ValidationForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: yupResolver(schema),
});
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} placeholder="Email" />
{errors.email && <p>{errors.email.message}</p>}
<input {...register("age")} placeholder="Age" type="number" />
{errors.age && <p>{errors.age.message}</p>}
<button type="submit">Submit</button>
</form>
);
}
Advanced Features
1. Dynamic Forms
function DynamicForm() {
const { control, handleSubmit } = useForm();
const { fields, append, remove } = useFieldArray({
control,
name: "items"
});
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
{fields.map((field, index) => (
<div key={field.id}>
<Controller
name={`items.${index}.value`}
control={control}
defaultValue={field.value}
render={({ field }) => <input {...field} />}
/>
<button type="button" onClick={() => remove(index)}>
Remove
</button>
</div>
))}
<button
type="button"
onClick={() => append({ value: '' })}
>
Add Item
</button>
<button type="submit">Submit</button>
</form>
);
}
2. File Uploads
function FileUploadForm() {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(data => {
const formData = new FormData();
formData.append("file", data.file[0]);
// Submit to API
})}>
<input type="file" {...register("file")} />
<button type="submit">Upload</button>
</form>
);
}
3. Conditional Fields
function ConditionalForm() {
const { register, watch } = useForm();
const showAddress = watch("showAddress");
return (
<form>
<label>
<input type="checkbox" {...register("showAddress")} />
Show address fields
</label>
{showAddress && (
<>
<input {...register("address.street")} placeholder="Street" />
<input {...register("address.city")} placeholder="City" />
</>
)}
</form>
);
}
Performance Optimization
1. Isolate Re-renders
const Input = React.memo(({ register, name, ...rest }) => (
<input {...register(name)} {...rest} />
));
function OptimizedForm() {
const { register } = useForm();
return (
<form>
<Input register={register} name="firstName" placeholder="First name" />
<Input register={register} name="lastName" placeholder="Last name" />
</form>
);
}
2. Debounced Inputs
function DebouncedForm() {
const { register, watch } = useForm();
const searchTerm = watch("search");
const [debouncedTerm] = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedTerm) {
// Perform search
}
}, [debouncedTerm]);
return (
<form>
<input {...register("search")} placeholder="Search..." />
</form>
);
}
Integration Patterns
1. With UI Libraries
function MuiForm() {
const { control } = useForm();
return (
<Controller
name="muiSelect"
control={control}
defaultValue=""
render={({ field }) => (
<Select {...field}>
<MenuItem value="option1">Option 1</MenuItem>
<MenuItem value="option2">Option 2</MenuItem>
</Select>
)}
/>
);
}
2. With State Management
function ZustandForm() {
const { user, updateUser } = useUserStore();
const { handleSubmit, register } = useForm({
defaultValues: user
});
return (
<form onSubmit={handleSubmit(updateUser)}>
<input {...register("name")} />
<input {...register("email")} />
</form>
);
}
Best Practices
- Default Values: Always provide default values for controlled components
useForm({
defaultValues: {
firstName: '',
lastName: ''
}
});
- Error Handling: Display helpful error messages
<input
{...register("age", {
validate: value => value > 18 || "Must be adult"
})}
/>
- Form Reset: Clean form after submission
const { reset } = useForm();
const onSubmit = async data => {
await submitToAPI(data);
reset();
};
- Accessibility: Ensure proper labeling
<label htmlFor="email">Email</label>
<input id="email" {...register("email")} />
- Type Safety: Use TypeScript for better developer experience
interface FormValues {
email: string;
password: string;
}
const { register } = useForm<FormValues>();
React Hook Form provides excellent performance out of the box by isolating re-renders and minimizing validation overhead. For most forms, it’s the most efficient solution available in the React ecosystem.