| Need | Use |
|---|---|
| Value, re-render, whole form/root component | watch() |
| Value, re-render, isolated to a small component | useWatch({ control, name }) |
| Value, no re-render at all | getValues() |
| Value, no re-render, reacting outside React | subscribe({ formState: { values: true }, callback }) |
| State (errors/dirty/valid), root component | formState from useForm() |
| State, isolated to a small component | useFormState({ control, name }) |
| One field's combined state | getFieldState(name) |
| Need | Use |
|---|---|
| One field, imperative | setValue(name, value, options) |
| Several fields at once, batched | setValues(partialValues, options) |
| Whole form, full reset | reset(values, options) |
| One field back to baseline | resetField(name, options) |
| Move the dirty-comparison baseline without touching current values | resetDefaultValues(values, options) |
<input>/<select>/<textarea> that exposes a real DOM ref → register.Controller / useController.| Symptom | Cause | Fix |
|---|---|---|
| First keystroke doesn't register | using value instead of defaultValue on a registered input | switch to defaultValue/defaultValues |
isValid stuck false/stale | no validation pass has run yet | needs a resolver, a mode beyond default onSubmit, or trigger() |
A setError'd error disappears | field is registered and its own rules pass on next validation | expected — setError doesn't outlive real validation on registered fields |
A setError'd error never clears | field was never registered | call clearErrors manually |
useFieldArray rows scramble on reorder | keyed by index instead of field.id | always key by field.id |
| Field array loses appended values on remount | shouldUnregister: true combined with useFieldArray | unsupported combination — don't combine them |
setValue/register on "a.b" silently no-ops | field 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 UI | Proxy tracking only activates on render-time reads | use useFormState for anything read conditionally/in a callback |
| Chapter/component re-renders on every keystroke elsewhere in a big form | reading watch()/root formState instead of the isolated hooks | switch to useWatch/useFormState scoped by name |
useForm per modal/tab and capture submitted data into shared state — don't rely on unmounted-field values surviving.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.