Capítulo 27 de 39

Chapter 27: useController

Core Idea

useController is the hook that powers Controller — it lets you build your own reusable controlled-input component with full access to field (value/handlers/ref), fieldState (per-field invalid/touched/dirty/error), and formState.

Key Concepts

  • name (required, reactive): the field path; changing it re-subscribes the controller to a different field, enabling dynamic field switching.
  • control: optional when inside FormProvider, otherwise required — the control object from useForm.
  • rules: same validation-rule shape as register's second argument (required, min, max, minLength, maxLength, pattern, validate).
  • shouldUnregister: defaults false; avoid true together with useFieldArray since unregister fires on every unmount/remount/reorder there.
  • exact: defaults true for useController (unlike useWatch/useFormState, which default exact to false) — controls whether the name subscription is an exact match.
  • field.onChange / field.onBlur / field.value / field.ref / field.name / field.disabled: the props to spread onto (or manually wire into) the controlled component — onChange reports the new value, onBlur reports interaction, ref lets RHF focus the input on a validation error.
  • defaultValue rule: never leave it undefined — either set it at the field level or provide defaultValues at useForm; an initially-undefined field.value makes the input start uncontrolled and React will warn when it later becomes controlled.

Code Examples

function Input({ control, name }) {
  const {
    field,
    fieldState: { invalid, isTouched, isDirty },
  } = useController({ name, control, rules: { required: true } })

  return (
    <TextField
      onChange={field.onChange}
      onBlur={field.onBlur}
      value={field.value}
      name={field.name}
      inputRef={field.ref}
    />
  )
}
  • What it demonstrates: wiring a third-party TextField (MUI) that doesn't forward a native ref the way RHF's register expects.

Reference Tables

PropResponsibility
onChangesend the new value back to RHF
onBlurreport the field was touched/blurred
valueset the input's current/initial value
reflet RHF focus the input on a validation error (requires React.forwardRef support, or an equivalent like MUI's inputRef)
namegive the input a unique registered name

Anti-patterns

  • Calling register on the same field a useController already manages: double-registers the input; use field directly, never both.
  • Multiple useController calls in one component without renaming field: each call subscribes independently, and unrenamed destructures collide; either rename each destructure (field: input, field: checkbox) or use Controller when you need several controlled fields in one component.
  • Combining field.onChange with local useState incorrectly: it's fine to keep local UI state alongside field, but always still call field.onChange so RHF's own state stays the source of truth for validation/submission.

Key Takeaways

  1. useController gives you the Controller component's exact mechanics as a hook — reach for it when building a reusable controlled-input component.
  2. Never register the same field twice — field already carries the full registration.
  3. Prefer one useController per component; use Controller (or renamed destructures) when you need more than one controlled field together.

Connects To

  • usecontroller-controller: the component built on top of this hook.
  • useform-control: where the control object passed in comes from.
  • useform-register: the uncontrolled counterpart — don't mix the two for the same field.