React Hook Form Reference
React Hook Form is a performant form library for form state, validation, and submission handling with minimal re-renders.
Imports and setup
interface FormData {
email: string;
}
export default function App() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>();
const onSubmit = (data: FormData) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div className="space-y-2">
<label htmlFor="email">Email</label>
<input id="email" type="email" {...register("email", { required: "Email is required" })} />
{errors.email && <p className="text-sm text-destructive">{errors.email.message}</p>}
</div>
<button type="submit">Submit</button>
</form>
);
}
Validation and state
Attach simple validation rules in register().
register("username", {
required: "Username is required",
minLength: { value: 3, message: "Minimum 3 characters" },
maxLength: { value: 20, message: "Maximum 20 characters" },
});
register("phone", {
pattern: { value: /^[0-9]{10}$/, message: "Invalid phone number" },
});
register("confirmPassword", {
validate: (value, formValues) =>
value === formValues.password || "Passwords don't match",
});
Use errors for field messages, isSubmitting to disable submit buttons, isDirty for changed forms, and isValid when the selected validation mode tracks validity.
Custom components
Use Controller for custom controls that do not behave like native inputs, such as Select, Checkbox, RadioGroup, and similar UI primitives.
function PriorityField() {
const { control } = useForm({ defaultValues: { priority: "medium" } });
return (
<Controller
name="priority"
control={control}
rules={ { required: "Choose a priority" } }
render={({ field }) => (
<select {...field}>
<option value="">Priority</option>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
)}
/>
);
}
Use watch for conditional fields, reset() after successful submission, and setValue() for programmatic updates.
const { watch, register, reset, setValue, getValues } = useForm();
const showOptional = watch("showOptional");
{showOptional && <input {...register("optionalField")} />}
reset();
reset({ name: "Jane" });
setValue("name", "John");
const values = getValues();
Zod validation
For reusable validation, combine React Hook Form with Zod and @hookform/resolvers/zod.
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const schema = z.object({
email: z.email({ error: "Invalid email" }),
password: z.string().min(8, { error: "Minimum 8 characters" }),
});
type FormData = z.infer<typeof schema>;
const form = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: { email: "", password: "" },
});
Best practices
Always provide defaultValues, use Controller for custom UI controls, display errors.fieldName.message below inputs, disable submit while isSubmitting, call reset() after successful submissions, and prefer Zod for validation that spans fields or needs a reusable runtime schema.