Module 6.2: React Hook Form

🎯 Learning Objectives
By the end of this lesson, you’ll master:
- React Hook Form (RHF): The most popular
form management libraryin the Reactecosystem. - Why RHF Matters: Solve the excessive re-rendering issue common with traditional Controlled Forms.
- Core API: Take command of
useForm,register,handleSubmit, andformState. - Validation: Integrate basic error checking.
1. Why Choose React Hook Form?
In complex forms, using useState for each input leads to re-rendering the entire component with every single keystroke.
React Hook Form tackles this by leveraging Uncontrolled Components (DOM) in conjunction with Refs to manage data, effectively eliminating unnecessary re-renders.

2. Setup
Open your project directory in the terminal and run:
npm install react-hook-form
# hoặc
yarn add react-hook-form
# hoặc
pnpm add react-hook-form
# hoặc
bun add react-hook-form3. Core Concepts (Core API)
React Hook Form provides powerful APIs for optimal form handling.
const { register, handleSubmit, watch, formState, reset, setValue, control } = useForm();3.1. register & Validation Rules
register is the most critical function, used to connect your input fields with RHF. It comes with built-in support for HTML5 validation standards.
<input {...register('firstName', { required: true, maxLength: 20 })} />| Rule | Description | Example |
| :---------------------- | :---------------------------- | :------------------------------------------------------- | --- | -------------- |
| required | Input is required | { required: "Cannot be empty" } |
| min/max | Minimum/Maximum numeric value | { min: { value: 18, message: "Must be over 18" } } |
| minLength/maxLength | String length | { minLength: 6 } |
| pattern | Regex validation | { pattern: /^\S+@\S+$/i } |
| validate | Custom validation function | { validate: value => value === "admin" | | "Nice try!" } |
3.2. formState Anatomy
formState holds information about the entire form’s state. Important: RHF uses Proxies, so you must destructure or directly access a property for RHF to subscribe to that state (which helps optimize renders).
errors: Anobjectcontainingvalidation errors.isSubmitting:truewhen the form is currently submitting (asynchronous).isDirty:truewhen the user has changed any value compared todefaultValues.isValid:truewhen there are novalidation errors(often used todisable the submit button).dirtyFields: Anobjecttracking specific fields that have been modified.
// This will only re-render when isDirty changes, not when errors change (if errors are not used)
const { isDirty, isSubmitting } = formState;3.3. handleSubmit
This wrapper function automatically prevents e.preventDefault() and collects form data.
const onSubmit = (data) => console.log(data);
const onError = (errors) => console.log(errors);
<form onSubmit={handleSubmit(onSubmit, onError)} />;3.4. watch vs getValues
watch("email"): Returns thefield's valueand re-renders thecomponentevery time thatfield changes. Use this for dynamicUI display.getValues("email"): Returns the current value but DOES NOT re-render. Use this inevent handlersorlogicthat doesn’t requireUI updates.
5. Advanced APIs & Techniques
5.1. Reset Form (reset)
Use this to clear form data or reset it to new default values (often after a successful submission or fetching data from an API).
const { reset } = useForm();
// Reset to empty
reset();
// Reset to new values (keeping formState if needed)
reset({ firstName: 'Luan', lastName: 'Thnh' }, { keepDirty: true });5.2. Manual Data Manipulation (setValue, trigger)
setValue("name", "value"): Sets afield's valuewithout user input.trigger("email"): Manually triggersvalidationfor a single field or the entire form.
<button onClick={() => setValue("age", 18)}>Set Age 18</button>
<button onClick={() => trigger("email")}>Validate Email Only</button>5.3. Integrating UI Libraries (Controller)
For complex components that aren’t native HTML inputs (like react-select, MUI DatePicker, AntD Input), we can’t use register (because there’s no ref pointing to a real input). In these cases, we use the <Controller /> component.
import { Controller } from 'react-hook-form';
<Controller
name="price"
control={control}
rules={{ required: true }}
render={({ field }) => (
// field contains: onChange, onBlur, value, ref
<input {...field} onChange={(e) = /> field.onChange(parseInt(e.target.value))} />
)}
/>;5.4. Performance Optimization
- Use
mode: 'onBlur': Onlyvalidatewhen the user leaves an input field to prevent continuous re-renders during typing (onChangemode). - Destructuring
formStateSmartly: Only extract what you truly need. For example, if you only needisSubmittingto display a loading indicator, don’t destructureerrorsif thatcomponentisn’t displaying errors. - Limit
watchUsage: Usingwatchexcessively in a parentcomponentwill trigger re-renders for the entire form. Instead, break down yourcomponentsand useuseWatchorcontrolto isolate re-renders.
5.5. Dynamic Forms with useFieldArray
Handle forms where the number of fields changes dynamically (e.g., adding multiple addresses, multiple family members).
The Challenge: If you use plain useState with an array, you’ll face difficulties managing Key IDs, validating individual fields, and performance (re-rendering the entire list).
The Solution: RHF’s useFieldArray.
import { useForm, useFieldArray } from 'react-hook-form';
const { control, register } = useForm();
const { fields, append, remove } = useFieldArray({
control,
name: 'items', // field name in formValues
});
return (
<ul>
{fields.map((item, index) => (
<li key={item.id}>
{/* Important: use item.id as the key */}
<input {...register(`items.${index}.name`)} />
<button onClick={() => remove(index)}>Delete</button>
</li>
))}
<button onClick={() => append({ name: 'New Item' })}>Add</button>
</ul>
);6. Hands-on Labs
Lab 1: Flight Booking Widget
Build a flight booking form with a card-style UI (Boarding Pass style).
- Features:
- Calculate
real-timeticket prices based onseat class(Class) and number ofpassengers(watch). - Input
Range Sliderfor the number of passengers. - Custom
Radio Buttonsfor selectingseat class.
- Calculate
- Techniques: Use
watchforlogic calculation,registerwith differentinput types(range,date,select).

💡 Hints:
- Use
watch(["class", "passengers"])to getreal-time valuesand multiply by the unit price.- Use CSS
peerto style customradio buttons.
Lab 2: Profile Settings Dashboard
Create a professional account settings page with complex conditional logic.
- Features:
Sidebarsimulating aDashboard UI.- Conditional Field: Only display “Hourly Rate” when the “Available for Hire” toggle is enabled.
- Character Counter: Display remaining
character countfor theBio. - Dirty State: Display “Unsaved Changes”
statewhen the user modifies theform.
- Techniques:
watchforconditional rendering,formState.isDirtyforUI feedback, NestedObject data(socials.twitter).

💡 Hints:
register("socials.twitter")for RHF to automatically create a nestedobjectupon submit.- Check
formState.isDirtytodisable the Save buttonif nochangeshave been made.
7. Recap
- Performance: React Hook Form makes your forms
fluidby minimizing re-renders. - Simplicity: The API is easy to use (
register,handleSubmit). - Integration: Easily integrate with
UI libraries(Material UI, AntD) viaController(which you’ll learn later). - Validation: Powerful
validation supportis available directly within theregister function.