Files
gruperly/.agents/skills/react-hook-form/references/formstate-avoid-isvalid-with-onsubmit.md
2026-09-04 16:49:24 -03:00

2.6 KiB

title, impact, impactDescription, tags
title impact impactDescription tags
Avoid isValid with onSubmit Mode for Button State MEDIUM prevents whole-form validation on every change under a deferred-validation mode formstate, isValid, onSubmit, validation-mode

Avoid isValid with onSubmit Mode for Button State

Subscribing to isValid opts the form into continuous validation. RHF only computes it when something reads it — _setValid() is gated on isValid being subscribed — but once it is, RHF runs the whole form's validation (the entire resolver schema, not just the changed field) on mount and again on every change event. Choosing mode: 'onSubmit' to defer validation and then reading isValid to grey out the submit button cancels out the deferral you asked for.

(The cost is per change event, not per render: re-rendering the component without touching a field does not re-validate.)

Incorrect (isValid re-validates the whole form on every change despite onSubmit mode):

function RegistrationForm() {
  const { register, handleSubmit, formState: { isValid } } = useForm<RegistrationData>({
    defaultValues: { email: '', password: '' },  // mode defaults to 'onSubmit'
  })

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email', { required: true })} />
      <input {...register('password', { required: true })} />
      <button disabled={!isValid}>Register</button>  {/* Opts the form into validating on every change */}
    </form>
  )
}

Correct (use isSubmitting or allow submit attempt):

function RegistrationForm() {
  const { register, handleSubmit, formState: { isSubmitting } } = useForm<RegistrationData>({
    defaultValues: { email: '', password: '' },
  })

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email', { required: true })} />
      <input {...register('password', { required: true })} />
      <button disabled={isSubmitting}>
        {isSubmitting ? 'Registering...' : 'Register'}
      </button>
    </form>
  )
}

Alternative: if a live-disabled submit button really is the requirement, say so explicitly rather than leaving the two settings in tension — pair isValid with a mode that already validates continuously:

function RegistrationForm() {
  const { register, formState: { isValid } } = useForm<RegistrationData>({
    mode: 'onChange',  // Deliberate: the button reflects validity as the user types
    defaultValues: { email: '', password: '' },
  })

  return <button disabled={!isValid}>Register</button>
}

Reference: useForm - mode