Capítulo 32 de 39
useFormState({ control, name?, exact? }) subscribes to form state in its own isolated scope, so a component reading e.g. isDirty re-renders on that change without affecting or being affected by other useFormState/useForm subscriptions elsewhere — the standard way to keep large forms' re-renders localized.
FormProvider, otherwise required.false (partial/prefix name matching) — different default from useController, which defaults exact to true.formState everywhere else in RHF, the object is a Proxy that only "activates" a subscription for properties actually read during render — always destructure at the top (const { isDirty } = useFormState()), never keep the whole object and read a property later.isDirty is a whole-form flag (can flip from field-array insert/remove without any single field being in dirtyFields); dirtyFields is per-field, and both require defaultValues to be set for correct comparison.function Child({ control }) {
const { dirtyFields } = useFormState({ control })
return dirtyFields.firstName ? <p>Field is dirty.</p> : null
}
Child re-renders only when dirtyFields changes, not on every keystroke elsewhere in the form.| formState field | Meaning |
|---|---|
isDirty | true once any input differs from its defaultValues |
dirtyFields | per-field modified map |
touchedFields | per-field interacted-with map |
isSubmitted / isSubmitSuccessful / submitCount | submission history |
isSubmitting | true while a submit is in flight |
isLoading | true while async defaultValues are loading |
isValid | true once validation has run at least once and found no errors — stays stale without a resolver or a mode other than onSubmit until trigger()/submit runs |
isValidating / validatingFields | in-flight validation tracking |
errors | field error object |
disabled | mirrors useForm's disabled option |
isReady | true once the formState subscription itself is set up |
const formState = useFormState() without destructuring: defeats the Proxy's per-field subscription tracking — always destructure the specific keys you need.isDirty alone to mean "this specific field changed": it's a whole-form signal; check dirtyFields.<name> for a per-field answer.useFormState's return immediately — reading a property later defeats its Proxy-based subscription.name to avoid re-rendering on unrelated field changes in large forms.isValid needs at least one validation pass (resolver, or a mode beyond default onSubmit, or an explicit trigger()) before it's meaningful.useForm() directly instead of the isolated hook.formState slice alongside field/fieldState.