</> useController: (UseControllerProps) => UseControllerReturn
This custom hook powers Controller. Additionally, it shares the same props and methods as Controller. It's useful for creating reusable Controlled input.
Props
The following table contains information about the arguments for useController.
| Name | Type | Required | Description |
|---|---|---|---|
name | FieldPath | ✓ | Unique name of your input. Reactive — the controller re-subscribes when this prop changes, allowing dynamic field name switching. |
control | Control | control object provided by invoking useForm. Optional when using FormProvider. | |
rules | Object | Validation rules in the same format for register, which includes: required, min, max, minLength, maxLength, pattern, validate.rules={{ required: true }} | |
shouldUnregister | boolean = false | Input will be unregistered after unmount and defaultValues will be removed as well. Note: this prop should be avoided when using with useFieldArray as unregister function gets called after input unmount/remount and reorder. | |
disabled | boolean = false | Since v7.46.0 disabled prop will be returned from field prop. Controlled input will be disabled and its value will be omitted from the submission data. | |
defaultValue | unknown | Important: Cannot apply undefined to defaultValue or defaultValues at useForm.
| |
exact | boolean = true | Since v7.68.0 This prop will enable an exact match for input name subscriptions, default to true. Note: this differs from useWatch and useFormState, which both default exact to false. |
Return
The following table contains information about properties which useController produces.
| Object Name | Name | Type | Description |
|---|---|---|---|
field | onChange | (value: any) => void | A function which sends the input's value to the library. It should be assigned to the onChange prop of the input and value should not be undefined. This prop updates formState and you should avoid manually invoking setValue or other API related to field update. |
field | onBlur | () => void | A function which sends the input's onBlur event to the library. It should be assigned to the input's onBlur prop. |
field | value | unknown | The current value of the controlled component. |
field | disabled | boolean | The disabled state of the input. |
field | name | string | Input's name being registered. |
field | ref | React.Ref | A ref used to connect hook form to the input. Assign ref to component's input ref to allow hook form to focus the error input. |
fieldState | invalid | boolean | Invalid state for current input. |
fieldState | isTouched | boolean | Touched state for current controlled input. |
fieldState | isDirty | boolean | Dirty state for current controlled input. |
fieldState | error | object | Since v7.0.0 error for this specific input. |
formState | isDirty | boolean | Set to true after the user modifies any of the inputs. Important: Make sure to provide all inputs' defaultValues at useForm, so hook form can have a single source of truth to compare whether the form is dirty. |
formState | dirtyFields | object | An object with the user-modified fields. Make sure to provide all inputs' defaultValues via useForm, so the library can compare against the defaultValues. |
formState | touchedFields | object | An object containing all the inputs the user has interacted with. |
formState | defaultValues | object | Since v7.37.0 The value that was set in useForm's defaultValues or updated defaultValues via reset API. |
formState | isSubmitted | boolean | Set to true after the form is submitted. Will remain true until the reset method is invoked. |
formState | isSubmitSuccessful | boolean | Indicates that the form was successfully submitted without any runtime error. |
formState | isSubmitting | boolean | true if the form is currently being submitted. false otherwise. |
formState | isLoading | boolean | Since v7.41.0 true if the form is currently loading async default values. Important: this prop is only applicable to async defaultValues. |
formState | submitCount | number | The number of times the form was submitted. |
formState | isValid | boolean | Set to true if the form doesn't have any errors. setError immediately forces isValid to false; this value is not itself derived from validation and will be overwritten the next time validation runs (e.g. on the next onChange, submit, or trigger() call). |
formState | isValidating | boolean | Set to true during validation. |
formState | validatingFields | object | Since v7.51.0 Captures fields that are undergoing asynchronous validation. |
formState | errors | object | An object with field errors. There is also an ErrorMessage component to retrieve error messages easily. |
formState | disabled | boolean | Since v7.48.0 Set to true if the form is disabled via the disabled prop in useForm. |
formState | isReady | boolean | Since v7.56.0 Set to true when formState subscription setup is ready. |
Examples
import { TextField } from "@mui/material"import { useController, useForm } from "react-hook-form"function Input({ control, name }) {const {field,fieldState: { invalid, isTouched, isDirty },formState: { touchedFields, dirtyFields },} = useController({name,control,rules: { required: true },})return (<TextFieldonChange={field.onChange} // send the value to hook formonBlur={field.onBlur} // notify when the input is touched or blurredvalue={field.value} // input valuename={field.name} // send the input nameinputRef={field.ref} // send input ref, so we can focus on the input when an error appears/>)}
Tips
-
It's important to be aware of each prop's responsibility when working with external controlled components, such as MUI, AntD, Chakra UI. Its job is to spy on the input, report, and set its value.
- onChange: send data back to hook form
- onBlur: report that the input has been interacted with (focus and blur)
- value: set up input initial and updated value
- ref: allow input to be focused with error — this only works if the target component forwards refs (
React.forwardRef) or exposes an equivalent, like MUI'sinputRef. If it doesn't accept a ref at all, omitreffrom what you spread and handle focus manually. - name: give input an unique name
It's fine to host your state and combined with
useController.const { field } = useController({ name: 'test' });const [value, setValue] = useState(field.value);onChange={(event) => {field.onChange(parseInt(event.target.value)) // data sent back to hook formsetValue(event.target.value) // UI state}} -
Do not
registerthe input again. This custom hook is designed to take care of the registration process.const { field } = useController({ name: 'test' })<input {...field} /> // ✅<input {...field} {...register('test')} /> // ❌ double up the registration -
It's best to use a single
useControllercall per component — each call creates its own subscription, so multiple calls in one component can cause extra re-renders. If you need more than one controlled field in the same component, rename the destructuredfieldfor each call to avoid naming collisions, or consider usingControllerinstead.const { field: input } = useController({ name: 'test' })const { field: checkbox } = useController({ name: 'test1' })<input {...input} /><input {...checkbox} />
Thank you for your support
If you find React Hook Form to be useful in your project, please consider starring and supporting it.