Cheatsheet

Cheatsheet — React Hook Form

Which read API?

NeedUse
Value, re-render, whole form/root componentwatch()
Value, re-render, isolated to a small componentuseWatch({ control, name })
Value, no re-render at allgetValues()
Value, no re-render, reacting outside Reactsubscribe({ formState: { values: true }, callback })
State (errors/dirty/valid), root componentformState from useForm()
State, isolated to a small componentuseFormState({ control, name })
One field's combined stategetFieldState(name)

Which write API?

NeedUse
One field, imperativesetValue(name, value, options)
Several fields at once, batchedsetValues(partialValues, options)
Whole form, full resetreset(values, options)
One field back to baselineresetField(name, options)
Move the dirty-comparison baseline without touching current valuesresetDefaultValues(values, options)

register vs Controller — which one?

  • Native <input>/<select>/<textarea> that exposes a real DOM ref → register.
  • Third-party component (MUI, react-select, date picker, anything without a native ref/value/onChange contract) → Controller / useController.
  • Never both on the same field.

Common trip-ups

SymptomCauseFix
First keystroke doesn't registerusing value instead of defaultValue on a registered inputswitch to defaultValue/defaultValues
isValid stuck false/staleno validation pass has run yetneeds a resolver, a mode beyond default onSubmit, or trigger()
A setError'd error disappearsfield is registered and its own rules pass on next validationexpected — setError doesn't outlive real validation on registered fields
A setError'd error never clearsfield was never registeredcall clearErrors manually
useFieldArray rows scramble on reorderkeyed by index instead of field.idalways key by field.id
Field array loses appended values on remountshouldUnregister: true combined with useFieldArrayunsupported combination — don't combine them
setValue/register on "a.b" silently no-opsfield was registered as a nested object (register("a", { value: {...} }))target the exact same shape/path that was registered
useFormContext().formState read in a callback never updates the UIProxy tracking only activates on render-time readsuse useFormState for anything read conditionally/in a callback
Chapter/component re-renders on every keystroke elsewhere in a big formreading watch()/root formState instead of the isolated hooksswitch to useWatch/useFormState scoped by name

Decision: Dialog-style field patterns

  • Modal/tab forms: build a separate useForm per modal/tab and capture submitted data into shared state — don't rely on unmounted-field values surviving.
  • Wizard forms: pair with an external store (or lift state) across route/step changes; RHF's own state is per-mount.

Performance defaults worth knowing

  • mode: "onChange" re-validates every keystroke — highest re-render cost of the built-in modes; prefer onBlur/onTouched/default onSubmit unless you need live feedback.
  • useWatch/useFormState/subscribe > watch/root formState in any form with more than a handful of fields.
  • setValues batches; several sequential setValue calls don't.