Files
gruperly/.agents/skills/react-hook-form/references/formstate-useformstate-isolation.md
2026-09-04 16:49:24 -03:00

2.2 KiB

title, impact, impactDescription, tags
title impact impactDescription tags
Use useFormState for Isolated State Subscriptions MEDIUM prevents parent re-renders from state access in children formstate, useFormState, isolation, re-renders

Use useFormState for Isolated State Subscriptions

useFormState allows subscribing to form state in child components without causing parent re-renders. Each useFormState instance is isolated and doesn't affect other subscribers.

Incorrect (formState at root re-renders entire form):

function ContactForm() {
  const { register, handleSubmit, formState: { errors, isDirty } } = useForm()

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email', { required: true })} />
      {errors.email && <span>Email required</span>}  {/* Re-renders all on any state change */}
      <input {...register('message')} />
      <SaveIndicator isDirty={isDirty} />  {/* Prop drilling */}
    </form>
  )
}

Correct (useFormState isolates subscriptions):

function ContactForm() {
  const { register, handleSubmit, control } = useForm()

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <EmailField register={register} control={control} />
      <input {...register('message')} />
      <SaveIndicator control={control} />  {/* Isolated subscription */}
    </form>
  )
}

function EmailField({ register, control }: EmailFieldProps) {
  const { errors } = useFormState({ control, name: 'email' })

  return (
    <div>
      <input {...register('email', { required: true })} />
      {errors.email && <span>Email required</span>}
    </div>
  )
}

function SaveIndicator({ control }: { control: Control }) {
  const { isDirty } = useFormState({ control })

  return isDirty ? <span>Unsaved changes</span> : null
}

Scope it further with name. An isolated useFormState({ control }) still re-renders on any field's state change. Pass name to narrow it to the fields the component actually displays:

function EmailFieldError({ control }: { control: Control<SignupFormValues> }) {
  const { errors } = useFormState({ control, name: 'email' })
  return errors.email ? <span>{errors.email.message}</span> : null
}

Reference: useFormState