Capítulo 28 de 39

Chapter 28: Controller

Core Idea

Controller is the component form of useController — a render-prop wrapper that connects a controlled third-party input (React-Select, AntD, MUI, react-datepicker, React Native's TextInput, …) to React Hook Form without you having to build the useController plumbing yourself.

Key Concepts

  • render prop: a function receiving { field, fieldState, formState }, returning the actual input element; field carries onChange/onBlur/value/name/ref/disabled to wire onto the target component.
  • name (required) / control: same as useControllercontrol is optional inside FormProvider.
  • rules / shouldUnregister / disabled / defaultValue / exact: identical semantics to useController's props (see that chapter) — exact defaults true here too.
  • Not needed for simple external control: if you only want to drive a field's value from outside the form, useForm's values option can do that without Controller at all.
  • field.value cannot be sent back as undefined: use null or "" instead, or the input silently flips between controlled/uncontrolled and React warns.
  • Transforming values in onChange: wrap field.onChange (e.g. parseInt(e.target.value)) to send a different type than the raw DOM event value, while still spreading the rest of field.

Code Examples

<Controller
  control={control}
  name="ReactDatepicker"
  render={({ field: { onChange, onBlur, value, ref } }) => (
    <ReactDatePicker onChange={onChange} onBlur={onBlur} selected={value} />
  )}
/>
  • What it demonstrates: adapting a third-party date picker (no native ref/value contract RHF understands) via the render prop.
<Controller
  name="test"
  render={({ field }) => (
    <input {...field} onChange={(e) => field.onChange(parseInt(e.target.value))} />
  )}
/>
  • What it demonstrates: sending an integer to RHF's internal state instead of the raw string the DOM event provides.

Anti-patterns

  • Spreading both {...field} and {...register('test')} onto the same input: double-registers it.
  • Returning undefined from a transformed onChange: use null/"" as the empty value instead.

Key Takeaways

  1. Controller is useController packaged as a render-prop component — pick whichever fits the calling component better.
  2. It's a "spy" on the input: onChange reports value, onBlur reports interaction, value sets current/initial state, ref enables focus-on-error (only if the target forwards refs), name uniquely identifies the field.
  3. Don't reach for Controller just to sync a field with external state — useForm's values option is the lighter tool for that.

Connects To

  • usecontroller: the hook this component wraps.
  • useform: the values option as an alternative for simple external-state syncing.
  • advanced-usage: the value-transform recipe extends the pattern shown here.