Capítulo 10 de 39

Chapter 10: formState

Core Idea

formState (from useForm() directly) tracks the whole form's interaction/validation status — dirty/touched fields, submission state, validity, and errors — wrapped in a Proxy that only subscribes to the specific properties you actually read.

Key Concepts

  • Proxy-based subscription: destructure the fields you need (const { isDirty } = formState); reading a property later (in a callback) or conditionally skips the subscription, so the component won't re-render on that change.
  • isDirty / dirtyFields: isDirty is whole-form (requires defaultValues to compare against); dirtyFields is per-field and can diverge from isDirty (e.g. field-array insert/remove changes isDirty without marking any individual field dirty).
  • isValid: only meaningful after at least one validation pass — with a resolver, or with mode beyond default onSubmit, or after an explicit trigger()/submit; setError immediately forces it false, but that's not derived from real validation and gets overwritten on the next validation run.
  • isReady (7.56.0+): true once the formState subscription itself is fully set up. A child component calling setValue in its own effect before this is ready can misbehave — gate such early writes on isReady.
  • isLoading: only meaningful with async defaultValues.

Code Examples

const {
  setValue,
  formState: { isReady },
} = useForm()

// Parent: safe
useEffect(() => setValue("test", "data"), [])

// Child component: gate on isReady
useEffect(() => isReady && setValue("test", "data"), [isReady])
  • What it demonstrates: why isReady matters specifically for child components writing to the form before the subscription is guaranteed set up.

Reference Tables

FieldMeaning
isDirty / dirtyFieldswhole-form vs per-field modification tracking
touchedFieldsper-field interaction tracking
defaultValuesthe values useForm/reset last set
isSubmitted / isSubmitSuccessful / submitCountsubmission history
isSubmittingtrue while a submit is in flight
isLoadingtrue while async defaultValues load
isValid / isValidating / validatingFieldsvalidity and in-flight validation tracking
errorsfield error object
disabledmirrors useForm's disabled option
isReadytrue once the subscription is fully initialized

Anti-patterns

  • const formState = useForm().formState kept around and read from later: defeats the Proxy — destructure at the point of use.
  • Calling setValue from a child's mount effect without checking isReady: can race the form's own subscription setup.

Key Takeaways

  1. Always destructure the specific formState keys you need, at render time.
  2. isValid needs a validation pass to be trustworthy — it isn't live-derived from nothing.
  3. isReady exists specifically to avoid races when a child writes to the form very early.

Connects To

  • useformstate: the isolated-re-render version of reading this same state.
  • useform-seterror: the one way to force isValid false without a real validation pass.