Skip to content


React hooks for controlled component

(props?: UseControllerProps) => { field: object, fieldState: object, formState: object }

This custom hook powers Controller. Additionally, it shares the same props and methods as Controller. It's useful for creating reusable Controlled input.


The following table contains information about the arguments for useController.

nameFieldPathUnique name of your input.
controlControlcontrol object provided by invoking useForm. Optional when using FormProvider.

Important: Can not apply undefined to defaultValue or defaultValues at useForm.

  • You need to either set defaultValue at the field-level or useForm's defaultValues. undefined is not a valid value.

  • If your form will invoke reset with default values, you will need to provide useForm with defaultValues.


Validation rules in the same format for register, which includes:

required, min, max, minLength, maxLength, pattern, validate

rules={{ required: true }}
shouldUnregisterboolean = 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.


The following table contains information about properties which useController produces.

Object NameNameTypeDescription
fieldonChange(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 update formState and you should avoid manually invoke setValue or other API related to field update.

fieldonBlur() => void

A function which sends the input's onBlur event to the library. It should be assigned to the input's onBlur prop.


The current value of the controlled component.


Input's name being registered.


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.


Invalid state for current input.


Touched state for current controlled input.


Dirty state for current controlled input.


error for this specific input.


Set to true after the user modifies any of the inputs.

  • Important: Make sure to provide all inputs' defaultValues at the useForm, so hook form can have a single source of truth to compare whether the form is dirty.

    const {
      formState: { isDirty, dirtyFields },
    } = useForm({ defaultValues: { test: "" } });
    // isDirty: true
    setValue('test', 'change')
    // isDirty: false because there getValues() === defaultValues
    setValue('test', '') 
  • File typed input will need to be managed at the app level due to the ability to cancel file selection and FileList 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.

  • Important: Make sure to provide defaultValues at the useForm, so hook form can have a single source of truth to compare each field's dirtiness.

  • Dirty fields will not represent as isDirty formState, because dirty fields are marked field dirty at field level rather the entire form. If you want to determine the entire form state use isDirty instead.

formStatetouchedFieldsobjectAn object containing all the inputs the user has interacted with.

The value which has been set at useForm's defaultValues or updated defaultValues via reset API.

formStateisSubmittedbooleanSet to true after the form is submitted. Will remain true until the reset method is invoked.

Indicate the form was successfully submitted without any runtime error.

formStateisSubmittingbooleantrue if the form is currently being submitted. false otherwise.

true if the form is currently loading async default values.

Important: this prop is only applicable to async defaultValues

const { 
  formState: { isLoading } 
} = useForm({ 
  defaultValues: async () => await fetch('/api') 
formStatesubmitCountnumberNumber of times the form was submitted.
Set to true if the form doesn't have any errors.

setError has no effect on isValid formState, isValid will always derived via the entire form validation result.

formStateisValidatingbooleanSet to true during validation.
formStateerrorsobjectAn object with field errors. There is also an ErrorMessage component to retrieve error message easily.


import { TextField } from "@material-ui/core";
import { useController, useForm } from "react-hook-form";

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

  return (
      onChange={field.onChange} // send value to hook form 
      onBlur={field.onBlur} // notify when input is touched/blur
      value={field.value} // input value
      name={} // send down the input name
      inputRef={field.ref} // send input ref, so we can focus on input when error appear
import * as React from "react";
import { useForm, useController, UseControllerProps } from "react-hook-form";

type FormValues = {
  FirstName: string;

function Input(props: UseControllerProps<FormValues>) {
  const { field, fieldState } = useController(props);

  return (
      <input {...field} placeholder={} />
      <p>{fieldState.isTouched && "Touched"}</p>
      <p>{fieldState.isDirty && "Dirty"}</p>
      <p>{fieldState.invalid ? "invalid" : "valid"}</p>

export default function App() {
  const { handleSubmit, control } = useForm<FormValues>({
    defaultValues: {
      FirstName: ""
    mode: "onChange"
  const onSubmit = (data: FormValues) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Input control={control} name="FirstName" rules={{ required: true }} />
      <input type="submit" />
import * as React from "react";
import { useController, useForm } from "react-hook-form";

const Checkboxes = ({ options, control, name }) => {
  const { field } = useController({
  const [value, setValue] = React.useState(field.value || []);

  return (
      {, index) => (
          onChange={(e) => {
            const valueCopy = [...value];

            // update checkbox value
            valueCopy[index] = ? : null;

            // send data to react hook form

            // update local state

export default function App() {
  const { register, handleSubmit, control } = useForm({
    defaultValues: {
      controlled: [],
      uncontrolled: []
  const onSubmit = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
        <input {...register("uncontrolled")} type="checkbox" value="A" />
        <input {...register("uncontrolled")} type="checkbox" value="B" />
        <input {...register("uncontrolled")} type="checkbox" value="C" />

          options={["a", "b", "c"]}
      <input type="submit" />


  • 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 input has been interacted (focus and blur)

    • value: set up input initial and updated value

    • ref: allow input to be focused with error

    • name: give input an unique name

    It's fine to host your state and combined with useController.

    const { field } = useController();
    const [value, setValue] = useState(field.value);
    onChange={(event) => {
      field.onChange(parseInt( // data send back to hook form
      setValue( // UI state
  • Do not register 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 ideal to use a single useController per component. If you need to use more than one, make sure you rename the prop. May want to consider using Controller instead.

    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 to star and support it.