Skip to Content
DocumentationForms and ValidationReact Hook Form

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 watch or useWatch for conditional logic without unnecessary re-renders.
  • TypeScript Friendly: Full type inference when used with schema libraries.

Why Use React Hook Form Instead of useState

ApproachProsCons
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 ManagementSimple 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

VariableDescription
registerRegister input fields for form state tracking. Works with native inputs.
controlRequired for controlled components (e.g., MUI TextField, custom components). Used with Controller.
handleSubmitWraps your submit function and handles validation automatically.
watchSubscribe to input value changes in real-time.
useWatchHook to watch specific fields outside the main form rendering context. See next section.
formStateObject containing status of the form: errors, isValid, isSubmitting, isDirty, touchedFields, etc.
resetReset form fields to default values or a new state.
setValueProgrammatically update a field value.
getValuesRetrieve 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 useWatch will trigger re-renders when their value changes, keeping the rest of the form efficient.


  • Use schema resolvers (zodResolver or yupResolver) for declarative validation.
  • Prefer Controller for non-native inputs like MUI or custom components.
  • Use useWatch for dynamic or conditional form logic without unnecessary re-renders.
  • Always type your forms with z.infer or yup.InferType for TypeScript safety.

References

For the most up-to-date and detailed information, please refer to the official documentation: