Comprehensive React Hook Form Documentation & Usage Guide
Overview
React Hook Form (RHF) is a lightweight library for building forms in React. It provides easy-to-use hooks to manage form state, validation, and submission, with minimal re-renders.
RHF works seamlessly with schema validation libraries like Zod and Yup, allowing you to validate forms declaratively while keeping full TypeScript support.
Key Features:
- Minimal Re-renders: Optimized for performance, especially in large forms.
- Schema Validation Support: Works with Zod/Yup via resolvers.
- Controlled & Uncontrolled Inputs: Supports both native inputs and custom components with
Controller. - Dynamic Field Watching: Use
watchoruseWatchfor conditional logic without unnecessary re-renders. - TypeScript Friendly: Full type inference when used with schema libraries.
Why Use React Hook Form Instead of useState
| Approach | Pros | Cons |
|---|---|---|
| React Hook Form | - Minimal re-renders - Works with large forms efficiently - Built-in validation support - Easy integration with Zod/Yup schemas | Slight learning curve for advanced features |
| useState / Manual Management | Simple for tiny forms | - Re-rendering each keystroke - Manual validation - Hard to scale for large or nested forms |
Summary: RHF is preferred for medium to large forms, especially when validation, conditional fields, or nested inputs are required.
Normal Usage with Zod Schema
Using RHF with a Zod schema is straightforward. Usage with Yup is almost identical, just replace zodResolver with yupResolver.
import React from "react";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { TextField, Button } from "@mui/material";
// Define Zod schema
const formSchema = z.object({
username: z.string().min(2, "Username must be at least 2 characters"),
email: z.string().email("Invalid email"),
password: z.string().min(6, "Password must be at least 6 characters"),
});
type FormValues = z.infer<typeof formSchema>;
export function MyForm() {
const {
handleSubmit,
control,
register,
watch,
formState: { errors, isValid, isSubmitting }
} = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: { username: "", email: "", password: "" },
mode: "onChange", // triggers validation on change
});
const onSubmit = (data: FormValues) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="username"
control={control}
render={({ field }) => (
<TextField {...field} label="Username" error={!!errors.username} helperText={errors.username?.message} />
)}
/>
<Controller
name="email"
control={control}
render={({ field }) => (
<TextField {...field} label="Email" error={!!errors.email} helperText={errors.email?.message} />
)}
/>
<Controller
name="password"
control={control}
render={({ field }) => (
<TextField {...field} label="Password" type="password" error={!!errors.password} helperText={errors.password?.message} />
)}
/>
<Button type="submit" disabled={!isValid || isSubmitting}>Submit</Button>
</form>
);
}Note: A detailed, step-by-step guide to integrating Zod with React Hook Form—including resolver setup, usage patterns, and advanced techniques—follows in the next section.
What useForm Returns
| Variable | Description |
|---|---|
register | Register input fields for form state tracking. Works with native inputs. |
control | Required for controlled components (e.g., MUI TextField, custom components). Used with Controller. |
handleSubmit | Wraps your submit function and handles validation automatically. |
watch | Subscribe to input value changes in real-time. |
useWatch | Hook to watch specific fields outside the main form rendering context. See next section. |
formState | Object containing status of the form: errors, isValid, isSubmitting, isDirty, touchedFields, etc. |
reset | Reset form fields to default values or a new state. |
setValue | Programmatically update a field value. |
getValues | Retrieve current form values without triggering a re-render. |
useWatch and When to Use It
useWatch allows you to subscribe to one or more field values without re-rendering the entire form.
Use Cases:
- Conditional rendering of fields based on other field values.
- Dynamic validation or formatting.
- Optimizing performance for large forms where re-rendering all fields is expensive.
import { useWatch, useForm } from "react-hook-form";
const { control } = useForm({ defaultValues: { role: "user", age: "" } });
// Watch the 'role' field
const role = useWatch({ control, name: "role" });
return (
<div>
{role === "admin" && <input placeholder="Admin secret code" />}
</div>
);Note: Only fields watched via
useWatchwill trigger re-renders when their value changes, keeping the rest of the form efficient.
Recommended Practices
- Use schema resolvers (
zodResolveroryupResolver) for declarative validation. - Prefer Controller for non-native inputs like MUI or custom components.
- Use
useWatchfor dynamic or conditional form logic without unnecessary re-renders. - Always type your forms with
z.inferoryup.InferTypefor TypeScript safety.
References
For the most up-to-date and detailed information, please refer to the official documentation: