Capítulo 6 de 39

Chapter 6: useForm

Core Idea

useForm(options?) is the entry point of the library — one hook call that returns every method (register, handleSubmit, control, watch, formState, …) needed to build, validate, and submit a form; its options object controls validation timing, default/reactive values, schema integration, and global behaviors like disabling or unregistering.

Key Concepts

  • mode: validation strategy before first submit — onSubmit (default, validates on submit then re-validates changed fields on change), onBlur, onChange (most re-renders), onTouched (first blur, then every change), or all (blur + change).
  • reValidateMode: validation strategy after submit for fields that already have errors; defaults to onChange. Both mode and reValidateMode are reactive since 7.56.0 — updating them after init takes effect on subsequent validations.
  • defaultValues: the whole form's initial values, sync or async (() => Promise<FieldValues>); cached, included in submission by default, reset only via reset(). Avoid undefined values and prototype-heavy objects (Moment/Luxon).
  • values: reactive values prop that overwrites defaultValues on change (server data, external state) unless resetOptions.keepDefaultValues is set.
  • errors: reactive server-error prop — must stay reference-stable across renders or it causes infinite re-render loops.
  • resetOptions: KeepStateOptions applied automatically when values/defaultValues update asynchronously (e.g. keepDirtyValues, keepErrors).
  • context: mutable object forwarded to a resolver's second argument (or Yup's validation context).
  • criteriaMode: firstError (default, one error per field) or all (collect every rule violation per field).
  • shouldFocusError: focuses the first errored field on failed submit (default true) — only works if that field's ref is attached to a real DOM element.
  • delayError: milliseconds to delay showing an error (correcting the input removes it instantly, delay isn't applied to the removal).
  • validate: form-level validation function ({formValues, formState, eventType, name}) => true | string | { field: {type, message} }; mutually exclusive with resolver (resolver wins if both set).
  • shouldUnregister: false by default (unmounted inputs keep their value and are skipped by built-in validation). Set to true for form behavior closer to native HTML forms — global setting only, not overridable at instance level for individual inputs; unmounted-input notification for shouldUnregister: true requires either registering inside useForm's own effect or reading the toggling condition via useWatch, not through a prop passed to a child that never re-renders.
  • shouldUseNativeValidation: drives the browser's Constraint Validation API (setCustomValidity/reportValidity) from RHF's own results; only with onSubmit/onChange modes and register/Controller-connected DOM refs; independent of progressive.
  • progressive: forwards validation rules (required, min, max, minLength, maxLength, pattern) as real HTML attributes, enabling usable forms before hydration.
  • disabled: disables the whole form and every registered input.
  • formControl: supply a pre-built control from createFormControl instead of letting useForm create its own internally.
  • resolver: plugs in Yup/Zod/Joi/Ajv/Vest/etc. (or a hand-written function) in place of built-in rules; cannot be combined with built-in validators on the same field; the returned errors object must be hierarchical, matching nested field paths ({ participants: [null, { name: err }] }), not dot-notation flat keys.

Code Examples

import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"

const schema = z.object({ name: z.string(), age: z.number() })

const { register, handleSubmit } = useForm({
  resolver: zodResolver(schema),
})
  • What it demonstrates: swapping built-in rules for a schema resolver — the most common useForm configuration beyond the defaults.

Anti-patterns

  • Depending on the whole methods object (from const methods = useForm()) in a useEffect dependency array: a future release memoizes the return so it changes reference on every formState update — depend on the specific destructured method (methods.reset, or better, const { reset } = useForm()) instead.
  • Passing an unstable errors prop: causes infinite re-renders since it's treated as a reactive update source.
  • Setting both resolver and validate: validate silently never runs.
  • Reading shouldUnregister-conditioned mount state from a plain prop instead of useWatch: the child never gets notified of the unmount, so RHF can't verify the input actually left the DOM.

Key Takeaways

  1. mode/reValidateMode control when validation runs, independently for before- and after-first-submit.
  2. resolver and validate are mutually exclusive; resolver always wins if both are configured.
  3. shouldUseNativeValidation and progressive are independent knobs — combine them if you want both the Constraint Validation API behavior and the raw HTML attributes present on the DOM.
  4. useForm() returns 19 named members (register, unregister, formState, watch, subscribe, handleSubmit, reset, resetField, resetDefaultValues, setError, clearErrors, setValue, setValues, setFocus, getValues, getErrors, getFieldState, trigger, control, Form) — each documented as its own chapter in this skill.

Connects To

  • useform-register / useform-handlesubmit / useform-formstate / useform-watch: the core loop most forms use directly.
  • createformcontrol: alternative source for the formControl option.
  • usecontroller / usefieldarray: consume the control value this hook returns.