Skip to content

API.V6

focuses on providing the best DX by simplifying the API.

React Hook Form V7 is released. If you are planning to upgrade, please read through the Migration Guide to V7.

useForm: Function

useForm also takes optional arguments. The following example demonstrates all of the arguments with their default values.

const { register } = useForm({
  mode: 'onSubmit',
  reValidateMode: 'onChange',
  defaultValues: {},
  resolver: undefined,
  context: undefined,
  criteriaMode: "firstError",
  shouldFocusError: true,
  shouldUnregister: true,
})type FormInputs = {
  firstName: string;
  lastName: string;
};

const { register } = useForm<FormInputs>({
  mode: 'onSubmit',
  reValidateMode: 'onChange',
  defaultValues: {},
  resolver: undefined,
  context: undefined,
  criteriaMode: "firstError",
  shouldFocusError: true,
  shouldUnregister: true,
})
mode: onChange | onBlur | onSubmit | onTouched | all = 'onSubmit'React Native: compatible with Controller
NameTypeDescription
onSubmit (Default)stringValidation will trigger on the submit event and invalid inputs will attach onChange event listeners to re-validate them.
onBlurstringValidation will trigger on the blur event.
onChangestringValidation will trigger on the change event with each input, and lead to multiple re-renders. Warning: this often comes with a significant impact on performance.
onTouchedstring

Validation will trigger on the first blur event. After that, it will trigger on every change event.

Note: when using with Controller, make sure to wire up onBlur with render prop.

allstringValidation will trigger on the blur and change events.
reValidateMode: onChange | onBlur | onSubmit = 'onChange'React Native: Custom register or using Controller

This option allows you to configure when inputs with errors get re-validated after submit. By default, validation is only triggered during an input change.

defaultValues: Record<string, any> = {}Video

The defaultValue for an input is used as the initial value when a component is first rendered, before a user interacts with it. The main difference between the input's defaultValue and value properties is that the value indicates the input from the user, whereas the defaultValue indicates the initial value as set by your application code. The defaultValue might reflect some "business logic" for your form (e.g. the default value for the field color is set to purple) or if your app allows editing previously saved form data it could be the previously saved value for the field of this input.

Note: It is encouraged that you set a defaultValue for all inputs to non-undefined such as the empty string or null.

You can set an input's default value with defaultValue/defaultChecked (read more from the React doc for Default Values), pass defaultValues as an optional argument to useForm() to populate the default values for the entire form, or set values on an individual component via its defaultValue property.

  • Important: defaultValues is cached at the first render within the custom hook. If you want to reset the defaultValues, you should use the api.

  • Values defined in defaultValues will be injected into as defaultValue.

  • It doesn't auto populate with the manually registered input (eg: register('test')) because the custom register field does not provide the ref to React Hook Form.

  • Its not default state for the form, to include additional form values:

    1. Register hidden input: <input type="hidden" ref={register} name="test" />

    2. Combine values at onSubmit callback.

const { register } = useForm({
  defaultValues: {
    firstName: "bill",
    lastName: "luo",
    email: "bluebill1049@hotmail.com",
    isDeveloper: true
  }
})

<input name="firstName" ref={register} /> // ✅ working version
<input name="lastName" ref={() => register({ name: 'lastName' })} />
// ❌ above example does not work with "defaultValues" due to its "ref" not being provided
type Inputs = {
  firstName: string;
  lastName: string;
  email: string;
  isDeveloper: boolean;
}
  
const { register } = useForm<Inputs>({
  defaultValues: {
    firstName: "bill",
    lastName: "luo",
    email: "bluebill1049@hotmail.com",
    isDeveloper: true
  }
})

<input name="firstName" ref={register} /> // ✅ working version
<input name="lastName" ref={() => register({ name: 'lastName' })} />
// ❌ above example does not work with "defaultValues" due to its "ref" not being provided
resolver: (values: any, context?: object) => Promise<ResolverResult> | ResolverResult

This function allows you to use any external validation library such as Yup, Zod, Joi, Superstruct and many others. Our goal is to make sure you can seamlessly integrate whichever validation library you prefer. If you're not using a library, you can always write your own logic to validate your forms.

At this time, we offer officially supported resolvers for: Yup, Zod, Joi and Superstruct.

npm install @hookform/resolvers

Notes on building a custom resolver:

  • Make sure you are returning an object that has values and errors properties. Their default values should be {}.

  • The keys of the error object should match the name value of your fields.

  • This function will be cached, while context is a mutable object which can be changed on each re-render.

  • Re-validation of an input will only occur one field at time during a user’s interaction. The lib itself will evaluate the errorobject to trigger a re-render accordingly.

  • Resolver can not be used with built-in (eg: required, min and etc) validator, and stick with either usage.

import React from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from "yup";

const schema = yup.object().shape({
  name: yup.string().required(),
  age: yup.number().required(),
}).required();

const App = () => {
  const { register, handleSubmit } = useForm({
    resolver: yupResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit(d => console.log(d))}>
      <input {...register("name")} />
      <input type="number" {...register("age")} />
      <input type="submit" />
    </form>
  );
};import React from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from "yup";

type Inputs = {
  name: string;
  age: string;
};

const schema = yup.object().shape({
  name: yup.string().required(),
  age: yup.number().required(),
}).required();

const App = () => {
  const { register, handleSubmit } = useForm<Inputs>({
    resolver: yupResolver(schema), // yup, joi and even your own.
  });

  return (
    <form onSubmit={handleSubmit(d => console.log(d))}>
      <input {...register("name")} />
      <input type="number" {...register("age")} />
      <input type="submit" />
    </form>
  );
};
import React from "react";
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 App = () => {
  const { register, handleSubmit } = useForm({
    resolver: zodResolver(schema)
  });

  return (
    <form onSubmit={handleSubmit(d => console.log(d))}>
      <input {...register("name")} />
      <input {...register("age")} type="number" />
      <input type="submit" />
    </form>
  );
};
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()
});

type Schema = z.infer<typeof schema>;

const App = () => {
  const { register, handleSubmit } = useForm<Schema>({
    resolver: zodResolver(schema)
  });

  const onSubmit = (data: Schema) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("name")} />
      <input {...register("age")} type="number" />
      <input type="submit" />
    </form>
  );
};
import React from 'react';
import { useForm } from 'react-hook-form';
import { joiResolver } from '@hookform/resolvers/joi';
import Joi from "joi";

const schema = Joi.object({
  name: Joi.string().required(),
  age: Joi.string().required(),
});

const App = () => {
  const { register, handleSubmit } = useForm({
    resolver: joiResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit(d => console.log(d))}>
      <input {...register("name")} />
      <input type="number" {...register("age")} />
      <input type="submit" />
    </form>
  );
};
import React from "react";
import { useForm } from "react-hook-form";
import { joiResolver } from "@hookform/resolvers/joi";
import Joi from "joi";

interface IFormInput {
  name: string;
  age: number;
}

const schema = Joi.object({
  name: Joi.string().required(),
  age: Joi.number().required()
});

const App = () => {
  const { register, handleSubmit, formState: { errors } } = useForm<IFormInput>({
    resolver: joiResolver(schema)
  });
  const onSubmit = (data: IFormInput) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("name"} />
      <input type="number" {...register("age"} />
      <input type="submit" />
    </form>
  );
}
import { useForm } from 'react-hook-form';
import { ajvResolver } from '@hookform/resolvers/ajv';

// must use `minLength: 1` to implement required field
const schema = {
  type: 'object',
  properties: {
    username: {
      type: 'string',
      minLength: 1,
      errorMessage: { minLength: 'username field is required' },
    },
    password: {
      type: 'string',
      minLength: 1,
      errorMessage: { minLength: 'password field is required' },
    },
  },
  required: ['username', 'password'],
  additionalProperties: false,
};

const App = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: ajvResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register('username')} />
      {errors.username && <p>{errors.username.message}</p>}
      <input {...register('password')} />
      {errors.password && <p>{errors.password.message}</p>}
      <button type="submit">submit</button>
    </form>
  );
};
import * as React from 'react';
import { useForm } from 'react-hook-form';
import { vestResolver } from '@hookform/resolvers/vest';
import vest, { test, enforce } from 'vest';

const validationSuite = vest.create((data = {}) => {
  test('username', 'Username is required', () => {
    enforce(data.username).isNotEmpty();
  });

  test('username', 'Must be longer than 3 chars', () => {
    enforce(data.username).longerThan(3);
  });

  test('password', 'Password is required', () => {
    enforce(data.password).isNotEmpty();
  });

  test('password', 'Password must be at least 5 chars', () => {
    enforce(data.password).longerThanOrEquals(5);
  });

  test('password', 'Password must contain a digit', () => {
    enforce(data.password).matches(/[0-9]/);
  });

  test('password', 'Password must contain a symbol', () => {
    enforce(data.password).matches(/[^A-Za-z0-9]/);
  });
});

const App = () => {
  const { register, handleSubmit } = useForm({
    resolver: vestResolver(validationSuite),
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register("username")} />
      <input {...register("password")} />
      <input type="submit" />
    </form>
  );
};
import * as React from "react";
import { useForm } from "react-hook-form";
import * as Joi from "joi";

const validationSchema = Joi.object({
  username: Joi.string()
    .alphanum()
    .min(3)
    .max(30)
    .required()
});

const App = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    context: "context",
    resolver: async (data, context) => {
      const { error, value: values } = validationSchema.validate(data, {
        abortEarly: false,
      });

      if (!error) return { values: values, errors: {} };

      return {
        values: {},
        errors: error.details.reduce(
          (previous, currentError) => ({
            ...previous,
            [currentError.path[0]]: currentError,
          }),
          {},
        ),
      };
    },
  });

  const onSubmit = data => {
    console.log(data)
  };

  return (
    <div className="App">
      <h1>resolver</h1>
      
      <form onSubmit={handleSubmit(onSubmit)}>
        <label>Username</label>
        <input {...register("username")} />
        {errors.username && <p>errors.username.message</p>}
        <input type="submit" />
      </form>
    </div>
  );
};
import * as React from "react";
import { useForm } from "react-hook-form";
import * as Joi from "joi";

interface IFormInputs {
  username: string;
}

const validationSchema = Joi.object({
  username: Joi.string()
    .alphanum()
    .min(3)
    .max(30)
    .required()
});

const App = () => {
  const { register, handleSubmit, formState: { errors } } = useForm<IFormInputs>({
    resolver: async data => {
      const { error, value: values } = validationSchema.validate(data, {
        abortEarly: false
      });

      return {
        values: error ? {} : values,
        errors: error
          ? error.details.reduce((previous, currentError) => {
              return {
                ...previous,
                [currentError.path[0]]: currentError
              };
            }, {})
          : {}
      };
    }
  });

  const onSubmit = (data: IFormInputs) => console.log(data);

  return (
    <div className="App">
      <h1>resolver</h1>

      <form onSubmit={handleSubmit(onSubmit)}>
        <label>Username</label>
        <input {...register("username")} />
        {errors.username && <p>errors.username.message</p>}
        <input type="submit" />
      </form>
    </div>
  );
};
shouldUnregister: boolean = true CodeSandbox

By default, when an input is removed, React Hook Form uses MutationObserver to detect and unregister the input(s) that are unmounted. However, you can set shouldUnregister to false to maintain the input state even when unmounting occurs.

  • Inputs state will have remained when unmounted and fall back to defaultValues when the value is undefined.

  • Inputs will no longer be able to unregister, this will disable unregister method as well.

truefalse
Can you unregister an input?
Value remains when input unmounts?
Is form state updated?
eg: isValid, isDirty, touched
❌ you will need to clear manually
context:
object

This context object is mutable and will be injected into resolver's second argument or Yup validation's context object.

CodeSandbox
criteriaMode
firstError | all
  • When set to firstError (default), only the first error from each field will be gathered.

  • When set to all, all errors from each field will be gathered.

CodeSandbox
shouldFocusError:
boolean = true

When set to true (default) and the user submits a form that fails the validation, it will set focus on the first field with an error.

Note: only registered fields with ref will work. Custom register inputs do not apply. eg: register('test') // doesn't work
However you're still able to use `forwardRef` to forward the ref and let the autofocus do its work.

Note: the focus order is based on the register order.

register: (Ref, RegisterOptions?) => voidReact Native: Custom register or using Controller

This method allows you to register an input/select Ref and apply validation rules into React Hook Form.

Validation rules are all based on HTML standard and also allow custom validation.

Important: name is required and unique (can not start with a number or use number as key name). Input name also supports dot and bracket syntax, which allows you to easily create nested form fields.

Input NameSubmit Result
name="firstName"{ firstName: 'value'}
name="name.firstName"{ name: { firstName: 'value' } }
name="name.firstName[0]"{ name: { firstName: [ 'value' ] } }

If you're working on simple Array Fields, you can assign an input name as name[index]. Check out the Field Array example. For more advance usage, make sure to checkout useFieldArray.

Custom Register

You can also register inputs manually, which is useful when working with custom components and Ref is not accessible. This is common when you are working with React Native or custom components like react-select. To make this easier, we provide a component to take care this process for you.

If you choose to not use Controller and manually register fields, you will need to update the input value with .

register('firstName', { required: true, min: 8 })

Note: If you want the custom registered input to trigger a re-render during its value update, then you should give a type to your registered input.

register({ name: 'firstName', type: 'custom' }, { required: true, min: 8 })

Note: If you have multiple radio inputs with the same name, you need to register the validation to the last input so the hook knows to validate them as a group at the end.

Register Options

By selecting the register option, the API table below will get updated.

NameDescriptionCode Examples
ref
React.RefObject
React element ref
<input name="test" ref={register} />
required
boolean
A Boolean which, if true, indicates that the input must have a value before the form can be submitted. You can assign a string to return an error message in the errors object.
<input
  name="test"
  ref={
    register({
      required: true
    })
  }
/>
maxLength
number
The maximum length of the value to accept for this input.
<input
  name="test"
  ref={
    register({
      maxLength: 2
    })
  }
/>
minLength
number
The minimum length of the value to accept for this input.
<input
  name="test"
  ref={
    register({
      minLength: 1
    })
  }
/>
max
number
The maximum value to accept for this input.
<input
  name="test"
  type="number"
  ref={
    register({
      max: 3
    })
  }
/>
min
number
The minimum value to accept for this input.
<input
  name="test"
  type="number"
  ref={
    register({
      min: 3
    })
  }
/>
pattern
RegExp

The regex pattern for the input.

Note: A RegExp object with the /g flag keeps track of the lastIndex where a match occurred.

<input
  name="test"
  ref={
    register({
      pattern: /[A-Za-z]{3}/
    })
  }
/>
validate
Function | Object
You can pass a callback function as the argument to validate, or you can pass an object of callback functions to validate all of them. (refer to the examples)
<input
  name="test"
  ref={
    register({
      validate: value => value === '1'
    })
  }
/>
// object of callback functions
<input
  name="test1"
  ref={
    register({
      validate: {
        positive: value => parseInt(value) > 0,
        lessThanTen: value => parseInt(value) < 10,
        asyncValidate: async () => await fetch(url)
      }
    })
  }
/>
valueAsNumber:
boolean

Returns a Number normally. If something goes wrong NaN will be returned.

Note: valueAs process is happening after validation.

<input
  name="test"
  type="number"
  ref={
    register({
      valueAsNumber: true,
    })
  }
/>
valueAsDate:
boolean

Returns a Date normally. If something goes wrong null will be returned.

Note: valueAs process is happening after validation.

<input
  name="test"
  type="date"
  ref={
    register({
      valueAsDate: true,
    })
  }
/>
setValueAs:
<T>(value: any) => T

Return input value by running through the function.

Note: valueAs process is happening after validation. Also, setValueAs is ignored if either valueAsNumber or valueAsDate are true.

<input
  name="test"
  ref={
    register({
      setValueAs: (value) => parseInt(value),
    })
  }
/>

unregister: (name: string | string[]) => void

This method allows you to unregister a single input or an array of inputs.

Note: when you unregister an input, its value will no longer be included in the form data that gets submitted.

import React, { useEffect } from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, handleSubmit, unregister } = useForm();
  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register} />
      <input name="lastName" ref={register} />
      <button type="button" onClick={() => unregister("lastName")}>unregister</button>
      <input type="submit" />
    </form>
  );
}import React, { useEffect } from "react";
import { useForm } from "react-hook-form";

interface IFormInputs {
  firstName: string;
  lastName?: string;
}

export default function App() {
  const { register, handleSubmit, unregister } = useForm<IFormInputs>();
  const onSubmit = (data: IFormInputs) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register} />
      <input name="lastName" ref={register} />
      <button type="button" onClick={() => unregister("lastName")}>unregister</button>
      <input type="submit" />
    </form>
  );
};


errors: Record<string, object>

Object containing form errors and error messages corresponding to each field.

NameTypeDescription
typestringError Type. eg: required, min, max, minLength
typesRecord<{ string, string | boolean }>

This is useful when you want to return all validation errors for a single input. For instance, a password field that is required to have a minimum length and contain a special character.

Note: You need to set criteriaMode to 'all' for this option to work.

messagestring | React.ReactElementIf you registered your input with an error message, then it will be put in this field, otherwise it's an empty string by default.
refReact.RefReference for your input element.

Important: Avoid using error object key names to avoid data overwrite.
eg: register('user'); register('user.type'); // error's type will get overwritten.

Note: You can use the component to help display your error states

CodeSandbox JS
import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, formState: { errors }, handleSubmit } = useForm();
  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("singleErrorInput", { required: true })} />
      {errors.singleErrorInput && "Your input is required"}

      {/* refer to the type of error to display message accordingly */}
      <input {...register("multipleErrorInput", { required: true, maxLength: 50 })} />
      {errors.multipleErrorInput?.type === "required" && "Your input is required"}
      {errors.multipleErrorInput?.type === "maxLength" && "Your input exceed maxLength"}

      {/* register with validation */}
      <input type="number" {...register("numberInput", { min: 50 })} />
      {errors.numberInput && "Your input required to be more than 50"}

      {/* register with validation and error message */}
      <input {...register("errorMessage", { required: "This is required" })} />
      {errors.errorMessage?.message}

      <input type="submit" />
    </form>
  );
}
import * as React from 'react'
import { useForm } from "react-hook-form";

interface IFormInputs {
  singleErrorInput: string
  multipleErrorInput: string
  numberInput: string
}

function App() {
  const { register, formState: { errors }, handleSubmit } = useForm<IFormInputs>();

  const onSubmit = (data: IFormInputs) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>Error</label>
      <input {...register("singleErrorInput", { required: true })} />
      {errors.singleErrorInput && <p>Your input is required</p>}

      <label>Error with type check</label>
      <input {...register("multipleErrorInput", { required: true, minLength: 5 })} />
      {errors.multipleErrorInput?.type === "required" && (
        <p>Your input is required</p>
      )}
      {errors.multipleErrorInput?.type === "minLength" && (
        <p>Your input must be larger then 3 characters</p>
      )}

      <label>Error with value</label>
      <input type="number" {...register("numberInput", { min: 50 })} />
      {errors.numberInput && <p>Your input required to be more than 50</p>}

      <input type="submit" />
    </form>
  );
}
import React from "react";
import { useForm } from "react-hook-form";
type Inputs = {
  a: number;
  b: string;
  c: Date;
  d: {
    e: string;
  };
  f: {
    g: number[];
    h: string[];
    i: { j: string }[];
  };
  k: any;
  l: any[];
  m: unknown;
  n: unknown[];
  o: object;
  p: object[];
  q: {
    r: any;
    s: {
      t: any[];
      u: unknown;
      v: object;
    }[];
    w: Date[];
    x: {
      y: {
        z: object[];
      };
    };
  };
};
export default function App() {
  const { formState: { errors } } = useForm<Inputs>();
  console.log(errors?.a?.message);
  console.log(errors?.b?.message);
  console.log(errors?.c?.message);
  console.log(errors?.d?.e?.message);
  console.log(errors?.f?.g && errors.f.g[0] && errors.f.g[0].message
  );
  console.log(errors?.f?.h && errors.f.h[0] && errors.f.h[0].message
  );
  console.log(
      errors?.f?.i &&
      errors?.f?.i[0] &&
      errors.f.i[0].j &&
      errors.f.i[0].j.message
  );
  console.log(errors?.k?.message);
  console.log(errors?.l?.message);
  console.log(errors?.m?.message);
  console.log(errors?.n && errors.n[0] && errors.n[0].message);
  console.log(errors?.o?.message);
  console.log(errors?.p && errors.p[0] && errors.p[0].message);
  console.log(errors?.q?.r?.message);
  console.log(
    errors?.q?.s && errors.q.s[0] && errors.q.s[0].t.message
  );
  console.log(
      errors?.q?.s &&
      errors.q.s[0] &&
      errors.q.s[0].u &&
      errors.q.s[0].u.message
  );
  console.log(
      errors?.q?.s &&
      errors.q.s[0] &&
      errors.q.s[0].v &&
      errors.q.s[0].v.message
  );
  console.log(errors?.q?.w && errors.q.w[0] && errors.q.w[0].message
  );
  console.log(
      errors?.q?.x?.y?.z &&
      errors.q.x.y.z[0] &&
      errors.q.x.y.z[0].message
  );
  return <form />;
}
import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, formState: { errors }, handleSubmit } = useForm({
    // by setting criteriaMode to 'all', 
    // all validation errors for single field will display at once
    criteriaMode: "all"
  });
  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        type="password"
        {...register("password", { required: true, minLength: 10, pattern: /\d+/ })}
      />
      {/* without enter data for the password input will result both messages to appear */}
      {errors?.password?.types?.required && <p>password required</p>}
      {errors?.password?.types?.minLength && <p>password minLength 10</p>}
      {errors?.password?.types?.pattern && <p>password number only</p>}

      <input type="submit" />
    </form>
  );
}
import { useForm } from "react-hook-form";

interface IFormInputs {
  password: string
}

export default function App() {
  const { register, formState: { errors }, handleSubmit } = useForm<IFormInputs>({
    // by setting criteriaMode to 'all',
    // all validation errors for single field will display at once
    criteriaMode: "all",
    mode: "onChange"
  });

  const onSubmit = (data: IFormInputs) => {
    console.log(data)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>Password</label>
      <input
        type="password"
        {...register("password", { required: true, minLength: 10, pattern: /d+/gi })}
      />
      {/* without enter data for the password input will result both messages to appear */}
      {errors?.password?.types?.required && <p>password required</p>}
      {errors?.password?.types?.minLength && <p>password minLength 10</p>}
      {errors?.password?.types?.pattern && <p>password number only</p>}

      <input type="submit" />
    </form>
  );
}

watch: (names?: string | string[]) => anyVideo

This will watch specified inputs and return their values. It is useful for determining what to render.

  • When defaultValue is not defined, the first render of watch will return undefined because it is called before register, but you can set the defaultValue as the second argument or provide defaultValues via useForm to avoid this behaviour.

  • If defaultValues was initialised in useForm as an argument, then the first render will return what's provided in defaultValues.

  • Note: when watching useFieldArray

    • When all inputs removed, it will return defaultValues, you can work around this issue by checking fields.length.

    • Input state live inside each field with uncontrolled form, and hence it's important to leave defaultValue for watch API. watch('fieldArray', fields)

TypeDescriptionExampleReturn
stringWatch input value by name (similar to lodash get function)watch('inputName')
watch('inputName', 'value')
any
string[]Watch multiple inputswatch(['inputName1'])
watch(['field1'], { field1: '1' })
{ [key:string] : any }
undefinedWatch all inputswatch()
watch(undefined, { field: '1' })
{ [key:string] : any }
CodeSandbox JS
import React from "react";
import { useForm } from "react-hook-form";

function App() {
  const { register, watch, errors, handleSubmit } = useForm();
  const watchShowAge = watch("showAge", false); // you can supply default value as second argument
  const watchAllFields = watch(); // when pass nothing as argument, you are watching everything
  const watchFields = watch(["showAge", "number"]); // you can also target specific fields by their names

  const onSubmit = data => console.log(data);

  return (
    <>
      <form onSubmit={handleSubmit(onSubmit)}>
        <input type="checkbox" name="showAge" ref={register} />
        
        {/* based on yes selection to display Age Input*/}
        {watchShowAge && <input type="number" name="age" ref={register({ min: 50 })} />}
        
        <input type="submit" />
      </form>
    </>
  );
}
import React from "react";
import { useForm } from "react-hook-form";

interface IFormInputs {
  name: string
  showAge: boolean
  age: number
}

function App() {
  const { register, watch, errors, handleSubmit } = useForm<IFormInputs>();
  const watchShowAge = watch("showAge", false); // you can supply default value as second argument
  const watchAllFields = watch(); // when pass nothing as argument, you are watching everything
  const watchFields = watch(["showAge", "number"]); // you can also target specific fields by their names

  const onSubmit = (data: IFormInputs) => console.log(data);

  return (
    <>
      <form onSubmit={handleSubmit(onSubmit)}>
        <input
          name="name"
          ref={register({ required: true, maxLength: 50 })}
        />
        <input type="checkbox" name="showAge" ref={register} />
        {/* based on yes selection to display Age Input*/}
        {watchShowAge && (
          <>
            <input type="number" name="age" ref={register({ min: 50 })} />
          </>
        )}
        <input type="submit" />
      </form>
    </>
  );
}
import React from "react";
import { useForm } from "react-hook-form";

type Inputs = {
  key1: string;
  key2: number;
  key3: {
    key1: number;
    key2: boolean;
  };
};

export default function App(props) {
  const { watch } = useForm<FormValues>;

  watch();
  // function watch(): FormValues
  watch({ nest: true });
  // function watch(option: { nest: boolean; }): FormValues
  watch("key1");
  // function watch<"key1">(field: "key1", defaultValue?: string | undefined): string
  watch("key1", "test");
  // function watch<"key1">(field: "key1", defaultValue?: string | undefined): string
  watch("key1", true);
  // ❌: type error
  watch("key3.key1");
  // function watch<unknown>(field: string, defaultValue?: unknown): unknown
  watch("key3.key1", 1);
  // function watch<1>(field: string, defaultValue?: 1 | undefined): number
  watch("key3.key1", "test");
  // function watch<"key3.key1", "test">(field: "key3.key1", defaultValue?: string | undefined): string
  watch("key3.key2", true);
  // function watch<true>(field: string, defaultValue?: true | undefined): boolean
  watch(["key1", "key2"]);
  // function watch<"key1" | "key2">(fields: ("key1" | "key2")[], defaultValues?: DeepPartial<Pick<FormValues, "key1" | "key2">> | undefined): Pick<FormValues, "key1" | "key2">
  watch(["key1", "key2"], { key1: "test" });
  // function watch<"key1" | "key2">(fields: ("key1" | "key2")[], defaultValues?: DeepPartial<Pick<FormValues, "key1" | "key2">> | undefined): Pick<FormValues, "key1" | "key2">
  watch(["key1", "key2"], { key1: "test", key2: true });
  // ❌: type error
  watch(["key1", "key3.key1"], { key1: "string" });
  // function watch(fields: string[], defaultValues?: DeepPartial<FormValues> | undefined): DeepPartial<FormValues>
  watch(["key1", "key3.key1"], { test: "string" });
  // ❌: type error
  watch<string, FormData["key3"]["key1"]>("key3.key1");
  //  => string
  watch<string, FormData["key3"]["key2"]>("key3.key2");
  //  => string
  
  return <form />;
}
import * as React from "react";
import { useForm, useFieldArray, ArrayField } from "react-hook-form";

function App() {
  const { register, control, handleSubmit, watch } = useForm();
  const { fields, remove, append } = useFieldArray({
    name: "test",
    control
  });
  const onSubmit = (data: FormValues) => console.log(data);
  
  console.log(watch("test")); 

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {fields.map((field, index) => {
        return (
          <input
            key={field.id}
            name={`test[${index}].firstName`}
            defaultValue={field.firstName}
            ref={register()}
          />
        );
      })}
      <button
        type="button"
        onClick={() =>
          append({
            firstName: "bill" + renderCount,
            lastName: "luo" + renderCount
          })
        }
      >
        Append
      </button>
    </form>
  );
}

handleSubmit:
((data: Object, e?: Event) => void, (errors: Object, e?: Event) => void) => Function

This function will pass the form data when form validation is successful and can be invoked remotely as well.

handleSubmit(onSubmit)()

Note: You can pass an async function for asynchronous validation. eg:

handleSubmit(async (data) => await fetchAPI(data))

import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, handleSubmit } = useForm();
  const onSubmit = (data, e) => console.log(data, e);
  const onError = (errors, e) => console.log(errors, e);

  return (
    <form onSubmit={handleSubmit(onSubmit, onError)}>
      <input name="firstName" ref={register} />
      <input name="lastName" ref={register} />
      <button type="submit">Submit</button>
    </form>
  );
}import React from "react";
import { useForm, SubmitHandler } from "react-hook-form";

type FormValues = {
  firstName: string;
  lastName: string;
  email: string;
};

export default function App() {
  const { register, handleSubmit } = useForm<FormValues>();
  const onSubmit: SubmitHandler<FormValues> = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register} />
      <input name="lastName" ref={register} />
      <input name="email" type="email" ref={register} />

      <input type="submit" />
    </form>
  );
}
import React from "react";
import { useForm } from "react-hook-form";

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

function App() {
  const { register, handleSubmit, errors, formState } = useForm();
  const onSubmit = async data => {
    await sleep(2000);
    if (data.username === "bill") {
      alert(JSON.stringify(data));
    } else {
      alert("There is an error");
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="username">User Name</label>
      <input name="username" placeholder="Bill" ref={register} />

      <input type="submit" />
    </form>
  );
}

reset: (values?: Record<string, any>, omitResetState?: Record<string, boolean>) => void

Reset the fields' values and errors. By supplying omitResetState, you have the freedom to only reset specific parts of the state. You can pass values as an optional argument to reset your form to the assigned default values.

  • For controlled components like React-Select which do not expose a ref prop, you will have to reset the input value manually with or by wrapping your component with .

  • You will need to pass defaultValues to useForm in order to reset the Controller components' value.

  • When you are subscribed/read the formState, it's important to decouple reset with handleSubmit, both are update formState and handleSubmit is async by default. You can check out a working example below:

  • When invoking reset({ value }) without supply defaultValues at useForm, hook form will replace defaultValues with shallow clone value object which you have supplied (not deepClone).

    // ❌ avoid the following with deep nested default values
    const defaultValues = { object: { deepNest: { file: new File() } } };
    useForm({ defaultValues });
    reset(defaultValues); // share the same reference
    
    // ✅ it's safer with the following, as we only doing shallow clone with defaultValues
    useForm({ deepNest: { file: new File() } });
    reset({ deepNest: { file: new File() } });
    
  • It's important to invoke reset after initializing useFieldArray the order matters. The reset API needs to aware of the field array shape before performing a reset properly.

import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, handleSubmit, reset } = useForm();
  const onSubmit = (data, e) => {};
  
  useEffect(async () => {
    const result = await fetch('./api/formValues.json'); // result: { firstName: 'test', lastName: 'test2' }
    reset(result); // asynchronously reset your form values
  }, [reset])

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register({ required: true })} />
      <input name="lastName" ref={register} />
      <input type="reset" /> // standard reset button
      <input type="button" onClick={() => reset({ firstName: "bill" }); }} /> // reset form with values
      <input type="button" onClick={() => {
        reset({
          firstName: "bill"
        }, {
          errors: true, // errors will not be reset 
          dirtyFields: true, // dirtyFields will not be reset
          isDirty: true, // dirty will not be reset
          isSubmitted: false,
          touched: false,
          isValid: false,
          submitCount: false,
        });
      }} />
    </form>
  );
}
import React from "react";
import { useForm } from "react-hook-form";

interface UseFormInputs {
  firstName: string
  lastName: string
}

export default function Form() {
  const { register, handleSubmit, reset, errors } = useForm<UseFormInputs>();
  const onSubmit = (data: UseFormInputs) => {
    console.log(data)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>First name</label>
      <input name="firstName" ref={register({ required: true })} />

      <label>Last name</label>
      <input name="lastName" ref={register} />

      <input type="submit" />
      <input
        type="reset"
        value="Standard Reset Field Values"
      />
      <input
        type="button"
        onClick={() => reset()}
        value="Custom Reset Field Values & Errors"
      />
    </form>
  );
}
import React from "react";
import { useForm, Controller } from "react-hook-form";
import { TextField } from "@material-ui/core";

export default function App() {
  const { register, handleSubmit, reset, setValue, control } = useForm();
  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller 
        as={TextField} 
        name="firstName"
        control={control} 
        rules={ required: true } 
        defaultValue=""
      />
      <Controller 
        as={TextField} 
        name="lastName"
        control={control}
        defaultValue="" 
      />
      
      <input type="submit" />
      <input type="button" onClick={reset} />
      <input
        type="button"
        onClick={() => {
          reset({
            firstName: "bill",
            lastName: "luo"
          });
        }}
      />
    </form>
  );
}
import React from "react";
import { useForm, Controller } from "react-hook-form";
import { TextField } from "@material-ui/core";

interface IFormInputs {
  firstName: string
  lastName: string
}

export default function App() {
  const { register, handleSubmit, reset, setValue, control } = useForm<IFormInputs>();
  const onSubmit = (data: IFormInputs) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller 
        as={TextField} 
        name="firstName"
        control={control} 
        rules={ required: true } 
        defaultValue=""
      />
      <Controller 
        as={TextField} 
        name="lastName"
        control={control}
        defaultValue="" 
      />
      
      <input type="submit" />
      <input type="button" onClick={reset} />
      <input
        type="button"
        onClick={() => {
          reset({
            firstName: "bill",
            lastName: "luo"
          });
        }}
      />
    </form>
  );
}
import React from "react";
import { useForm, useFieldArray, Controller } from "./src";

function App() {
  const {
    register,
    handleSubmit,
    reset,
    formState: { isSubmitSuccessful }
  } = useForm({ defaultValues: { something: "anything" } });
  const [submittedData, setSubmittedData] = React.useState({});

  const onSubmit = (data) => {
    setSubmittedData(data);
  };

  React.useEffect(() => {
    if (isSubmitSuccessful) {
      reset({ ...submittedData });
    }
  }, [isSubmitSuccessful, submittedData, reset]);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="something" ref={register} />
      <input type="submit" />
    </form>
  );
}
import React, { useEffect } from "react";
import { useForm, useFieldArray, Controller } from "react-hook-form";

function App() {
  const { register, control, handleSubmit, reset } = useForm({
    defaultValues: {
      loadState: "unloaded",
      names: [{ firstName: "Bill", lastName: "Luo" }]
    }
  });
  const { fields, remove } = useFieldArray({
    control,
    name: "names"
  });

  // it's import to invoke reset after useFieldArray
  useEffect(() => {
    setTimeout(() => {
      reset({
        names: [
          {
            firstName: "Bill1",
            lastName: "Luo1"
          },
        ]
      });
    }, 1000);
  }, [reset]);

  const onSubmit = (data) => console.log("data", data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <ul>
        {fields.map((item, index) => (
          <li key={item.id}>
            <input
              name={`names[${index}].firstName`}
              defaultValue={`${item.firstName}`}
              ref={register()}
            />

            <Controller
              as={<input />}
              name={`names[${index}].lastName`}
              control={control}
              defaultValue={item.lastName}
            />
          </li>
        ))}
      </ul>

      <input type="submit" />
    </form>
  );
}

setError:
(name: string, error: { type?: string, types?: object, message?: string, shouldFocus?: boolean }) => void

The function allows you to manually set one or more errors.

  • This method will not persist the associated input error if the input passes validation.

  • An error that is not associated with an input field will be persisted until cleared with clearErrors.

    Note: for TypeScript users, please consider this approach for setting server-side error or outside declared field type.

  • Can be useful in the handleSubmit method when you want to give error feedback to a user after async validation. (ex: API returns validation errors)

  • shouldFocusError doesn't work with input been disabled.

import React from "react";
import { useForm } from "react-hook-form";

const App = () => {
  const { register, handleSubmit, setError, errors } = useForm();
  const onSubmit = data => {
    console.log(data)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        name="username"
        onChange={() => {
          setError("username", {
            type: "manual",
            message: "Dont Forget Your Username Should Be Cool!"
          });
        }}
        ref={register}
      />
      {errors.username && <p>{errors.username.message}</p>}
      <input type="submit" />
    </form>
  );
};import * as React from "react";
import { useForm } from "react-hook-form";

type FormInputs = {
  username: string;
};

const App = () => {
  const { register, handleSubmit, setError, errors } = useForm<FormInputs>();
  const onSubmit = (data: FormInputs) => {
    console.log(data)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        name="username"
        onChange={() => {
          setError("username", {
            type: "manual",
            message: "Dont Forget Your Username Should Be Cool!"
          });
        }}
        ref={register}
      />
      {errors.username && <p>{errors.username.message}</p>}
      <input type="submit" />
    </form>
  );
};
import * as React from "react";
import { useForm } from "react-hook-form";

const App = () => {
  const { register, handleSubmit, setError, errors } = useForm();

  const onSubmit = data => {
    console.log(data)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>Username</label>
      <input
        name="username"
        onChange={() => {
          setError("username", {
            type: "manual",
            message: "Dont Forget Your Username Should Be Cool!"
          });
        }}
        ref={register}
      />
      {errors.username && <p>{errors.username.message}</p>}
      <label>First Name</label>
      <input name="firstName" ref={register} />
      {errors.firstName && <p>{errors.firstName.message}</p>}
      <button
        type="button"
        onClick={() => {
          [
            {
              type: "manual",
              name: "username",
              message: "Double Check This"
            },
            {
              type: "manual",
              name: "firstName",
              message: "Triple Check This"
            }
          ].forEach(({ name, type, message }) =>
            setError(name, { type, message })
          );
        }}
      >
        Trigger Name Errors
      </button>
      <input type="submit" />
    </form>
  );
};
import * as React from "react";
import { useForm } from "react-hook-form";

type FormInputs = {
  username: string;
  firstName: string;
};

const App = () => {
  const { register, handleSubmit, setError, errors } = useForm<FormInputs>();

  const onSubmit = (data: FormInputs) => {
    console.log(data)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>Username</label>
      <input
        name="username"
        onChange={() => {
          setError("username", {
            type: "manual",
            message: "Dont Forget Your Username Should Be Cool!"
          });
        }}
        ref={register}
      />
      {errors.username && <p>{errors.username.message}</p>}
      <label>First Name</label>
      <input name="firstName" ref={register} />
      {errors.firstName && <p>{errors.firstName.message}</p>}
      <button
        type="button"
        onClick={() => {
          [
            {
              type: "manual",
              name: "username",
              message: "Double Check This"
            },
            {
              type: "manual",
              name: "firstName",
              message: "Triple Check This"
            }
          ].forEach(({ name, type, message }) =>
            setError(name, { type, message })
          );
        }}
      >
        Trigger Name Errors
      </button>
      <input type="submit" />
    </form>
  );
};
import * as React from "react";
import { useForm } from "react-hook-form";

const App = () => {
  const { register, handleSubmit, setError, errors } = useForm({
    criteriaMode: 'all',
  });
  
  const onSubmit = data => {
    console.log(data)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>Last Name</label>
      <input
        name="lastName"
        ref={register}
        onChange={() => {
          setError("lastName", {
            types: {
              required: "This is required",
              minLength: "This is minLength"
            }
          });
        }}
      />
      {errors.lastName && errors.lastName.types && (
        <p>{errors.lastName.types.required}</p>
      )}
      {errors.lastName && errors.lastName.types && (
        <p>{errors.lastName.types.minLength}</p>
      )}
      <input type="submit" />
    </form>
  );
};
import * as React from "react";
import { useForm } from "react-hook-form";

type FormInputs = {
  lastName: string;
};

const App = () => {
  const { register, handleSubmit, setError, errors } = useForm<FormInputs>({
    criteriaMode: 'all',
  });
  
  const onSubmit = (data: FormInputs) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>Last Name</label>
      <input
        name="lastName"
        ref={register}
        onChange={() => {
          setError("lastName", {
            types: {
              required: "This is required",
              minLength: "This is minLength"
            }
          });
        }}
      />
      {errors.lastName && errors.lastName.types && (
        <p>{errors.lastName.types.required}</p>
      )}
      {errors.lastName && errors.lastName.types && (
        <p>{errors.lastName.types.minLength}</p>
      )}
      <input type="submit" />
    </form>
  );
};

clearErrors: (name?: string | string[]) => void

  • undefined: reset all errors

  • string: reset the error on a single field or by key name.

    register('test.firstName', { required: true });
    register('test.lastName', { required: true });
    clearErrors('test'); // will clear both errors from test.firstName and test.lastName
    clearErrors('test.firstName'); // for clear single input error
    
  • string[]: reset errors on the given fields

import * as React from "react";
import { useForm } from "react-hook-form";

const App = () => {
  const { register, errors, handleSubmit, clearErrors } = useForm();

  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register({ required: true })} />
      <input name="lastName" ref={register({ required: true })} />
      <input name="username" ref={register({ required: true })} />
      <button type="button" onClick={() => clearErrors("firstName")}>
        Clear First Name Errors
      </button>
      <button
        type="button"
        onClick={() => clearErrors(["firstName", "lastName"])}
      >
        Clear First and Last Name Errors
      </button>
      <button type="button" onClick={() => clearErrors()}>
        Clear All Errors
      </button>
      <input type="submit" />
    </form>
  );
};
import * as React from "react";
import { useForm } from "react-hook-form";

import "./styles.css";

type FormInputs = {
  firstName: string;
  lastName: string;
  username: string;
};

const App = () => {
  const { register, errors, handleSubmit, clearErrors } = useForm<FormInputs>();

  const onSubmit = (data: FormInputs) => {
    console.log(data)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register({ required: true })} />
      <input name="lastName" ref={register({ required: true })} />
      <input name="username" ref={register({ required: true })} />
      <button type="button" onClick={() => clearErrors("firstName")}>
        Clear First Name Errors
      </button>
      <button
        type="button"
        onClick={() => clearErrors(["firstName", "lastName"])}
      >
        Clear First and Last Name Errors
      </button>
      <button type="button" onClick={() => clearErrors()}>
        Clear All Errors
      </button>
      <input type="submit" />
    </form>
  );
};

setValue: (name: string, value: any, config?: Object) => void

This function allows you to dynamically set the value of a registered field. At the same time, it tries to avoid unnecessary rerender. Only the following conditions will trigger a re-render:

  • When an error is triggered by a value update

  • When an error is corrected by a value update

  • When setValue is invoked for the first time and formState.isDirty is set to true

You can also set the shouldValidate parameter to true in order to trigger a field validation.

setValue('name', 'value', { shouldValidate: true })

You can also set the shouldDirty parameter to true in order to set field to dirty.

setValue('name', 'value', { shouldDirty: true })

It's recommended to target field name instead using second argument with nested object.

setValue('yourDetails.firstName', 'value'); // ✅ performant
setValue('yourDetails', { firstName: 'value' }); // less performant 
CodeSandbox JS
import * as React from "react";
import { useForm } from "react-hook-form";

const App = () => {
  const { register, handleSubmit, setValue } = useForm();

  const onSubmit = data => {
    console.log(data)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register} />
      <input name="lastName" ref={register} />
      <button onClick={() => setValue("firstName", "Bill")}>
        Set First Name Value
      </button>
      <button
        onClick={() =>
          setValue("lastName", "Luo", {
            shouldValidate: true,
            shouldDirty: true
          })
        }
      >
        Set Last Name
      </button>
      <input type="submit" />
    </form>
  );
};
import * as React from "react";
import { useForm } from "react-hook-form";

type FormInputs = {
  firstName: string
  lastName: string
}

const App = () => {
  const { register, handleSubmit, setValue } = useForm<FormInputs>();

  const onSubmit = (data: FormInputs) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register} />
      <input name="lastName" ref={register} />
      <button onClick={() => setValue("firstName", "Bill")}>
        Set First Name Value
      </button>
      <button
        onClick={() =>
          setValue("lastName", "Luo", {
            shouldValidate: true,
            shouldDirty: true
          })
        }
      >
        Set Last Name
      </button>
      <input type="submit" />
    </form>
  );
};
import React from "react";
import { useForm } from "react-hook-form";

type FormValues = {
  string: string;
  number: number;
  object: {
    number: number;
    boolean: boolean;
  };
  array: {
    string: string;
    boolean: boolean;
  }[];
};

export default function App() {
  const { setValue } = useForm<FormValues>();
  
  setValue("string", "test");
  // function setValue<"string", string>(name: "string", value: string, shouldValidate?: boolean | undefined): void
  setValue("number", 1);
  // function setValue<"number", number>(name: "number", value: number, shouldValidate?: boolean | undefined): void
  setValue("number", "error");
  
  return <form />;
}
import React from "react";
import { useForm, useFieldArray } from "react-hook-form";

const ChildComponent = ({ index, control, register }) => {
  const { fields } = useFieldArray({
    name: `nest[${index}].nestedArray`,
    control
  });

  return (
    {fields.map((item, i) => (
      <input
        key={item.id}
        name={`nest[${index}].nestedArray[${i}].value`}
        ref={control.register()}
        defaultValue={item.value}
      />
    ))}
  );
};

export default () => {
  const { register, control, setValue } = useForm({
    defaultValues: {
      nest: [
        { value: "1", nestedArray: [{ value: "2" }] },
        { value: "3", nestedArray: [{ value: "4" }] }
      ]
    }
  });
  const { fields } = useFieldArray({ name: "nest", control });

  React.useEffect(() => {
    setValue("nest", [
      {
        value: 0,
        nestedArray: [
          {
            value: 8
          }
        ]
      }
    ]);
  }, [setValue]);

  return (
    <>
      {fields.map((item, i) => (
        <div key={item.id}>
          <input
            name={`nest[${i}].value`}
            ref={register()}
            defaultValue={item.value}
          />
          <ChildComponent control={control} register={register} index={i} />
        </div>
      ))}
    </>
  );
};

getValues: (payload?: string | string[]) => Object

An optimized helper for reading form values. The difference between watch and getValues is that getValues will not trigger re-renders or subscribe to input changes.

Important: You shouldn't use this method inside render. This is suitable for reading values in an event handler.

  • getValues(): Read all form values.

CodeSandbox JS
import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, getValues } = useForm();

  return (
    <form>
      <input name="test" ref={register} />
      <input name="test1" ref={register} />

      <button
        type="button"
        onClick={() => {
          const values = getValues(); // { test: "test-input", test1: "test1-input" }
          const singleValue = getValues("test"); // "test-input"
          const multipleValues = getValues(["test", "test1"]);
          // { test: "test-input", test1: "test1-input" }
        }}
      >
        Get Values
      </button>
    </form>
  );
}import React from "react";
import { useForm } from "react-hook-form";

type FormInputs = {
  test: string
  test1: string
}

export default function App() {
  const { register, getValues } = useForm<FormInputs>();

  return (
    <form>
      <input name="test" ref={register} />
      <input name="test1" ref={register} />

      <button
        type="button"
        onClick={() => {
          const values = getValues(); // { test: "test-input", test1: "test1-input" }
          const singleValue = getValues("test"); // "test-input"
          const multipleValues = getValues(["test", "test1"]);
          // { test: "test-input", test1: "test1-input" }
        }}
      >
        Get Values
      </button>
    </form>
  );
}import React from "react";
import { useForm } from "react-hook-form";

// Flat input values
type Inputs = {
  key1: string;
  key2: number;
  key3: boolean;
  key4: Date;
};

export default function App() {
  const { register, getValues } = useForm<Inputs>();
  
  getValues();
  
  return <form />;
}

// Nested input values
type Inputs1 = {
  key1: string;
  key2: number;
  key3: {
    key1: number;
    key2: boolean;
  };
  key4: string[];
};

export default function Form() {
  const { register, getValues } = useForm<Inputs1>();
  
  getValues();
  // function getValues(): Record<string, unknown>
  getValues("key1");
  // function getValues<"key1", unknown>(payload: "key1"): string
  getValues("key2");
  // function getValues<"key2", unknown>(payload: "key2"): number
  getValues("key3.key1");
  // function getValues<"key3.key1", unknown>(payload: "key3.key1"): unknown
  getValues<string, number>("key3.key1");
  // function getValues<string, number>(payload: string): number
  getValues<string, boolean>("key3.key2");
  // function getValues<string, boolean>(payload: string): boolean
  getValues("key4");
  // function getValues<"key4", unknown>(payload: "key4"): string[]

  return <form />;
}

trigger: (payload?: string | string[]) => Promise<boolean>

Manually triggers form validation.

  • trigger(): Triggers validation on all fields.

  • trigger('test'): Triggers validation on a specific field value by name.

  • trigger(['test', 'test1']): Triggers validation on multiple fields by name.

import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, trigger, errors } = useForm();

  return (
    <form>
      <input name="firstName" ref={register({ required: true })} />
      <input name="lastName" ref={register({ required: true })} />
      <button type="button" onClick={() => { trigger("lastName"); }}>Trigger</button>
      <button type="button" onClick={() => { trigger(["firstName", "lastName"]); }}>Trigger Multiple</button>
      <button type="button" onClick={() => { trigger(); }}>Trigger All</button>
      <button
        type="button"
        onClick={async () => {
          const result = await trigger("lastName");
          if (result) { console.log("valid input") }
        }}
      >
        Trigger Result
      </button>
    </form>
  );
}import React from "react";
import { useForm } from "react-hook-form";

type FormInputs = {
  firstName: string
  lastName: string
}

export default function App() {
  const { register, trigger, errors } = useForm<FormInputs>();

  return (
    <form>
      <input name="firstName" ref={register({ required: true })} />
      <input name="lastName" ref={register({ required: true })} />
      <button type="button" onClick={() => { trigger("lastName"); }}>Trigger</button>
      <button type="button" onClick={() => { trigger(["firstName", "lastName"]); }}>Trigger Multiple</button>
      <button type="button" onClick={() => { trigger(); }}>Trigger All</button>
      <button
        type="button"
        onClick={async () => {
          const result = await trigger("lastName");
          if (result) { console.log("valid input") }
        }}
      >
        Trigger Result
      </button>
    </form>
  );
}

control: Object

This object contains methods for registering components into React Hook Form.

import React from "react";
import { useForm, Controller } from "react-hook-form";

function App() {
  const { control } = useForm();
  return (
    <Controller
      as={<input />}
      name="firstName"
      control={control}
      defaultValue=""
    />
  );
}
import React from "react";
import { useForm, Controller } from "react-hook-form";
import { TextField } from "@material-ui/core";

type FormInputs = {
  firstName: string
}

function App() {
  const { control, handleSubmit } = useForm<FormInputs>();
  const onSubmit = (data: FormInputs) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        as={TextField}
        name="firstName"
        control={control}
        defaultValue=""
      />
      
      <input type="submit" />
    </form>
  );
}

formState: Object

This object contains information about the form state. If you want to subscribe to formState update at useEffect, make sure that you place the entire formState in the optional array.

Important: formState is wrapped with Proxy to improve render performance, so make sure you invoke or read it before render in order to enable the state update.

useEffect(() => {
  if (formState.errors.firstName) {
    // do the your logic here
  }
}, [formState]); // ✅ 
// ❌ formState.errors will not trigger the useEffect        
import { useForm } from "react-hook-form";

export default function App () {
  const {
    register,
    handleSubmit,
    formState
  } = useForm();

  const onSubmit = (data) => console.log(data);

  React.useEffect(() => {
    console.log("touchedFields", formState.touchedFields);
  },[formState]); // use entire formState object as optional array arg in useEffect, not individual properties of it


  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("test")} />
      <input type="submit" />
    </form>
  );
};

import React from "react";
import { useForm } from "react-hook-form";
type FormInputs = {
  test: string
}
export default function App() {
  const {
    register,
    handleSubmit,
    formState
  } = useForm<FormInputs>();
  const onSubmit = (data: FormInputs) => console.log(data);
  
  React.useEffect(() => {
    console.log("touchedFields", formState.touchedFields);
  }, [formState]);
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("test")} />
      <input type="submit" />
    </form>
  );
}
// ❌ formState.isValid is accessed conditionally, 
// so the Proxy does not subscribe to changes of that state
return <button disabled={!formState.isDirty || !formState.isValid} />;
  
// ✅ read all formState values to subscribe to changes
const { isDirty, isValid } = formState;
return <button disabled={isDirty || isValid} />;
NameTypeDescription
isDirtyboolean

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

  • 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.
  • File typed input will need to manage at app level due to the ability to cancel file selection and FileList object.

  • Native inputs will return string type by default.

dirtyFieldsobject

An object with the user-modified fields.

Make sure to provide all inputs' defaultValues at the useForm, so hook form can compare with the defaultValue.

touchedobjectAn object containing all the inputs the user has interacted with.
isSubmittedbooleanSet to true after the form is submitted. Will remain true until the reset method is invoked.
isSubmitSuccessfulboolean

Indicate the form was successfully submitted.

isSubmittingbooleantrue if the form is currently being submitted. false if otherwise.
submitCountnumberNumber of times the form was submitted.
isValidboolean
Set to true if the form doesn't have any errors.

Note: isValid is affected by mode. This state is only applicable with onChange and onBlur mode.

isValidatingbooleanSet to true during validation.
errorsobjectAn object with field errors.
import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const {
    register,
    handleSubmit,
    errors,
    // Read the formState before render to subscribe the form state through Proxy
    formState: { isDirty, isSubmitting, touched, submitCount },
  } = useForm();
  const onSubmit = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="test" ref={register} />
      <input type="submit" />
    </form>
  );
}
import React from "react";
import { useForm } from "react-hook-form";

type FormInputs = {
  test: string
}

export default function App() {
  const {
    register,
    handleSubmit,
    errors,
    // Read the formState before render to subscribe the form state through the Proxy
    formState: { isDirty, isSubmitting, touched, submitCount },
  } = useForm<FormInputs>();
  const onSubmit = (data: FormInputs) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="test" ref={register} />
      <input type="submit" />
    </form>
  );
}

Controller: Component

React Hook Form embraces uncontrolled components and native inputs, however it's hard to avoid working with external controlled component such as React-Select, AntD and MUI. This wrapper component will make it easier for you to work with them.

NameTypeRequiredDescription
namestringUnique name of your input.
controlObjectcontrol object is from invoking useForm. Optional when using FormProvider.
renderFunction

This is a render prop. A function that returns a React element and provides the ability to attach events and value into the component. This simplifies integrating with external controlled components with non-standard prop names. Provides onChange, onBlur, name, ref and value to the child component.

<Controller
  control={control}
  name="test"
  render={(
    { onChange, onBlur, value, name, ref },
    { invalid, isTouched, isDirty }
  ) => (
    <Checkbox
      onBlur={onBlur}
      onChange={(e) => onChange(e.target.checked)}
      checked={value}
      inputRef={ref}
    />
  )}
/>
<Controller
  render={(props) => (
    <TextField
      value={props.value}
      onChange={props.onChange}
      inputRef={props.ref} // wire up the input ref
    />
  )}
  name="TextField"
  control={control}
  rules={{ required: true }}
/>
asReact.ElementType

Controller will inject onChange, onBlur and value props into the component.

Every prop you pass to the Controller component will be forwarded to the component instance you provided with the as prop. For instance, if you have a custom Switch component that requires a label prop, you can pass it to the Controller component directly and it will take care of forwarding the prop for you.

  • The following props will be passed into your component:
    onChange, onBlur, value, name, ref

  • For TypeScript user, if you are using custom component with required fields: onChange, onBlur, value, It will complain about missing declared field. Please use render instead of as.

<Controller
  // props: onChange, onBlur, ref, name, value
  as={<TextInput />}
  control={control}
  name="test"
/>
defaultValueanyThe same as an uncontrolled component's defaultValue. When passing a boolean value, it will be treated as checkbox input. For more details, see useForm's defaultValues section.
  • You need to either set defaultValue at the field-level or call useForm with defaultValues.

    Note: inline defaultValue is required when working with useFieldArray by integrating with the value from fields object.

  • If your form will invoke reset with default values, you will need to call useForm with defaultValues instead of setting the defaultValue on individual fields.

rulesObjectValidation rules in the same format as for register.
rules={{ required: true }}
onFocus() => void

This callback allows the custom hook to focus on the input when there is an error. This function is applicable for both React and React-Native components as long as they can be focused.

  • Input focus order is according to registration order, which means native input registered with ref will be the first to be focused on the error due to Controller registration occurred during useEffect.

  • If your input disabled based on form.formState.isSubmitting, you must enabled it before invoke focus method.

<Controller
  onFocus={() => {
    // when input is been disabled.
    inputRef.current.disabled = false;
    inputRef.current?.focus();
  }}
/>
CodeSandbox
import React from "react";
import ReactDatePicker from "react-datepicker";
import { TextField } from "@material-ui/core";
import { useForm, Controller } from "react-hook-form";

function App() {
  const { handleSubmit, control } = useForm();

  return (
    <form onSubmit={handleSubmit(data => console.log(data))}>
      <Controller as={TextField} name="TextField" control={control} defaultValue="" />
      
      <Controller
        control={control}
        name="ReactDatepicker"
        render={({ onChange, onBlur, value }) => (
          <ReactDatePicker
            onChange={onChange}
            onBlur={onBlur}
            selected={value}
          />
        )}
      />
      
      <input type="submit" />
    </form>
  );
}
import React from "react";
import ReactDatePicker from "react-datepicker";
import { TextField } from "@material-ui/core";
import { useForm, Controller } from "react-hook-form";

type FormValues = {
  TextField: string;
  ReactDatepicker: string;
} 

function App() {
  const { handleSubmit, control } = useForm<FormValues>();

  return (
    <form onSubmit={handleSubmit(data => console.log(data))}>
      <Controller as={TextField} name="TextField" control={control} defaultValue="" />
      
      <Controller
        control={control}
        name="ReactDatepicker"
        render={({ onChange, onBlur, value }) => (
          <ReactDatePicker
            onChange={onChange}
            onBlur={onBlur}
            selected={value}
          />
        )}
      />
      
      <input type="submit" />
    </form>
  );
}
import { Text, View, TextInput, Button, Alert } from "react-native";
import { useForm, Controller } from "react-hook-form";

export default function App() {
  const { control, handleSubmit, formState: { errors } } = useForm({
    defaultValues: {
      firstName: '',
      lastName: ''
    }
  });
  const onSubmit = data => console.log(data);

  return (
    <View>
      <Controller
        control={control}
        rules={{
         required: true,
        }}
        render={({ field: { onChange, onBlur, value } }) => (
          <TextInput
            style={styles.input}
            onBlur={onBlur}
            onChangeText={onChange}
            value={value}
          />
        )}
        name="firstName"
      />
      {errors.firstName && <Text>This is required.</Text>}

      <Controller
        control={control}
        rules={{
         maxLength: 100,
        }}
        render={({ field: { onChange, onBlur, value } }) => (
          <TextInput
            style={styles.input}
            onBlur={onBlur}
            onChangeText={onChange}
            value={value}
          />
        )}
        name="lastName"
      />

      <Button title="Submit" onPress={handleSubmit(onSubmit)} />
    </View>
  );
}

useController: (arguments: UseControllerProps) => { field: object, meta: object }

This custom hook is what powers Controller, and shares the same props and methods as Controller. It's useful to create reusable Controlled input, while Controller is the flexible option to drop into your page or form.

Please refer Controller section for this hook's arguments. It shares the same arguments except as and renders.

React.RefObject
Object NameNameTypeDescription
fieldonChange(value: any) => void

A function which send value to hook form and should be assigned with onChange prop.

onBlur(value: any) => void

A function which send value to hook form and should be assigned with onBlur prop.

valueunknown

The value of this controlled component.

ref

Assign ref to component's input ref, so hook form can focus on the error input.

metainvalidboolean

Invalid state for current input.

isTouchedboolean

Touched state for current controlled input.

isDirtyboolean

Dirty state for current controlled input.

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

function Input({ control, name }) {
  const {
    field: { ref, ...inputProps },
    meta: { invalid, isTouched, isDirty },
  } = useController({
    name,
    control,
    rules: { required: true },
    defaultValue: "",
  });

  return <TextField {...inputProps} inputRef={ref} />;
}

function App() {
  const { control } = useForm();
  
  return <Input name="firstName" control={control} />;
}
import * as React from "react";
import { useForm, useController } from "./src";

function Input(props) {
  const { field, meta } = useController(props);

  return (
    <div>
      <input {...field} placeholder={props.name} />
      <p>{meta.isTouched && "Touched"}</p>
      <p>{meta.isDirty && "Dirty"}</p>
      <p>{meta.invalid ? "invalid" : "valid"}</p>
    </div>
  );
}

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Input control={control} name="FirstName" rules={{ required: true }} />
      <input type="submit" />
    </form>
  );
}

ErrorMessage: Component

A simple component to render associated input's error message.

npm install @hookform/error-message
NameTypeRequiredDescription
namestringName of the field.
errorsobjecterrors object from React Hook Form. Optional if you are using FormProvider.
messagestring | React.ReactElementInline error message.
asReact.ElementType | stringWrapper component or HTML tag. eg: as="span" or as={<Text />}
render({ message: string | React.ReactElement, messages?: Object}) => anyThis is a render prop for rendering error message or messages.

Note: you need to set criteriaMode to 'all' for using messages.

import React from "react";
import { useForm } from "react-hook-form";
import { ErrorMessage } from '@hookform/error-message';

export default function App() {
  const { register, errors, handleSubmit } = useForm();
  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="singleErrorInput" ref={register({ required: "This is required." })} />
      <ErrorMessage errors={errors} name="singleErrorInput" />
      
      <ErrorMessage
        errors={errors}
        name="singleErrorInput"
        render={({ message }) => <p>{message}</p>}
      />
      
      <input type="submit" />
    </form>
  );
}
import React from "react";
import { useForm } from "react-hook-form";
import { ErrorMessage } from '@hookform/error-message';

interface FormInputs {
  singleErrorInput: string
}

export default function App() {
  const { register, formState: { errors }, handleSubmit } = useForm<FormInputs>();
  const onSubmit = (data: FormInputs) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("singleErrorInput", { required: "This is required." })} />
      <ErrorMessage errors={errors} name="singleErrorInput" />
      
      <ErrorMessage
        errors={errors}
        name="singleErrorInput"
        render={({ message }) => <p>{message}</p>}
      />
      
      <input type="submit" />
    </form>
  );
}
import React from "react";
import { useForm } from "react-hook-form";
import { ErrorMessage } from '@hookform/error-message';

interface FormInputs {
  singleErrorInput: string
}

export default function App() {
  const { register, errors, handleSubmit } = useForm<FormInputs>();
  const onSubmit = (data: FormInputs) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="singleErrorInput" ref={register({ required: "This is required." })} />
      <ErrorMessage errors={errors} name="singleErrorInput" />
      
      <ErrorMessage
        errors={errors}
        name="singleErrorInput"
        render={({ message }) => <p>{message}</p>}
      />
      
      <input type="submit" />
    </form>
  );
}
import React from "react";
import { useForm } from "react-hook-form";
import { ErrorMessage } from '@hookform/error-message';

interface FormInputs {
  multipleErrorInput: string
}

export default function App() {
  const { register, formState: { errors }, handleSubmit } = useForm<FormInputs>({
    criteriaMode: "all"
  });
  const onSubmit = (data: FormInputs) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register("multipleErrorInput", {
          required: "This is required.",
          pattern: {
            value: /d+/,
            message: "This input is number only."
          },
          maxLength: {
            value: 10,
            message: "This input exceed maxLength."
          }
        })}
      />
      <ErrorMessage
        errors={errors}
        name="multipleErrorInput"
        render={({ messages }) =>
          messages &&
          Object.entries(messages).map(([type, message]) => (
            <p key={type}>{message}</p>
          ))
        }
      />

      <input type="submit" />
    </form>
  );
}

useFormContext: Function

This custom hook allows you to access the form context. useFormContext is intended to be used in deeply nested structures, where it would become inconvenient to pass the context as a prop.

You need to wrap your form with the FormProvider component for useFormContext to work properly.

Return

This hook will return all the useForm return methods and props.

const methods = useForm()
      
<FormProvider {...methods} /> // all the useForm return props
      
const methods = useFormContext() // retrieve those props

Rules

You need to wrap your form with the FormProvider component for useFormContext to work properly.

Examples

import React from "react";
import { useForm, FormProvider, useFormContext } from "react-hook-form";

export default function App() {
  const methods = useForm();
  const onSubmit = data => console.log(data);

  return (
    <FormProvider {...methods} > // pass all methods into the context
      <form onSubmit={methods.handleSubmit(onSubmit)}>
        <NestedInput />
        <input type="submit" />
      </form>
    </FormProvider>
  );
}

function NestedInput() {
  const { register } = useFormContext(); // retrieve all hook methods
  return <input {...register("test")} />;
}

useWatch:
({ control?: Control, name?: string, defaultValue?: unknown, disabled?: boolean }) => object

Behaves similarly to the watch API, however, this will isolate re-rendering at the custom hook level and potentially result in better performance for your application.

Props

NameTypeDescription
namestring | string[] | undefinedName of the field.
controlObjectcontrol object provided by useForm. It's optional if you are using FormContext.
defaultValueunknown

default value for useWatch to return before the initial render.

Note: the first render will always return defaultValue when it's supplied.

disabledboolean = false

Option to disable the subscription.

exactboolean = false

This prop will enable an exact match for input name subscriptions.

Return

ExampleReturn
useWatch('inputName')unknown
useWatch(['inputName1'])unknown[]
useWatch(){[key:string]: unknown}

Rules

  • The initial return value from useWatch will always return what's inside of defaultValue or defaultValues from useForm.

  • The only difference between useWatch and watch is at the root (useForm) level or the custom hook level update.

  • useWatch's execution order matters, which means if you update a form value before the subscription is in place, then the value updated will be ignored.

    setValue('test', 'data');
    useWatch({ name: 'test' }); // ❌ subscription is happened after value update, no update received
    
    useWatch({ name: 'example' }); // ✅ input value update will be received and trigger re-render
    setValue('example', 'data'); 
    
  • useWatch's result is optimised for render phase instead of useEffect's deps, to detect value updates you may want to use an external custom hook for value comparison.

Examples

import React from "react";
import { useForm, useWatch } from "react-hook-form";

function Child({ control }) {
  const firstName = useWatch({
    control,
    name: "firstName",
  });

  return <p>Watch: {firstName}</p>;
}

function App() {
  const { register, control } = useForm({
    firstName: "test"
  });
  
  return (
    <form>
      <input {...register("firstName")} />
      <Child control={control} />
    </form>
  );
}
import React from "react";
import { useForm, useWatch } from "react-hook-form";

interface FormInputs {
  firstName: string;
  lastName: string;
}

function FirstNameWatched({ control }: { control: Control<FormInputs> }) {
  const firstName = useWatch({
    control,
    name: "firstName", // without supply name will watch the entire form, or ['firstName', 'lastName'] to watch both
    defaultValue: "default" // default value before the render
  });

  return <p>Watch: {firstName}</p>; // only re-render at the custom hook level, when firstName changes
}

function App() {
  const { register, control, handleSubmit } = useForm<FormInputs>();

  const onSubmit = (data: FormInputs) => {
    console.log(data)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>First Name:</label>
      <input {...register("firstName")} />
      <input {...register("lastName")} />
      <input type="submit" />

      <FirstNameWatched control={control} />
    </form>
  );
}
import React from "react";
import { useWatch } from "react-hook-form";

function totalCal(results) {
  let totalValue = 0;

  for (const key in results) {
    for (const value in results[key]) {
      if (typeof results[key][value] === "string") {
        const output = parseInt(results[key][value], 10);
        totalValue = totalValue + (Number.isNaN(output) ? 0 : output);
      } else {
        totalValue = totalValue + totalCal(results[key][value], totalValue);
      }
    }
  }

  return totalValue;
}

export const Calc = ({ control, setValue }) => {
  const results = useWatch({ control, name: "test" });
  const output = totalCal(results);

  // isolated re-render to calc the result with Field Array
  console.log(results);

  setValue("total", output);

  return <p>{output}</p>;
};


useFieldArray:
({ control?: Control, name: string, keyName?: string = 'id' }) => objectVideo

Custom hook for working with uncontrolled Field Arrays (dynamic inputs). The motivation is to provide better user experience and form performance. You can watch this short video to compare controlled vs uncontrolled Field Array.

NameTypeRequiredDescription
namestring

Name of the field. Important: make sure name is in object shape: name=test[index].name as we don't support flat arrays.

controlObjectcontrol object provided by useForm. It's optional if you are using FormContext.
keyNamestring = 'id'field array key value, default to "id", you can change the key name.
function Test() {
  const { control, register } = useForm();
  const { fields, append, prepend, remove, swap, move, insert } = useFieldArray({
    control, // control props comes from useForm (optional: if you are using FormContext)
    name: "test", // unique name for your Field Array
    // keyName: "id", default to "id", you can change the key name
  });

  return (
    {fields.map((field, index) => (
      <input
        key={field.id} // important to include key with field's id
        name={`test[${index}].value`}
        ref={register()} // register() when there is no validation rules 
        defaultValue={field.value} // make sure to include defaultValue
      />
    ))}
  );
}

Important: To be able to efficiently manipulate arrays of uncontrolled components useFieldArray has some rules you need to be aware of:

  • The field.id (and not index) must be added as the component key to prevent re-renders breaking the fields:

    // ✅ correct:
    {fields.map((field, index) => (
      <div key={field.id}>
        <input ... />
      </div>
    ))}
    // ✅ correct:
    {fields.map((field, index) => <input key={field.id} ... />)}
    // ❌ incorrect:
    {fields.map((field, index) => <input key={index} ... />)}
    

  • defaultValue must be set for all inputs. Supplied defaultValues in the useForm hook will prepare the fields object with default value.

  • You can not call actions one after another. Actions need to be triggered per render.

    // ❌ The following is not correct
    handleChange={() => {
      if (fields.length === 2) {
        remove(0);
      }
      append({ test: 'test' });
    }}
    
    // ✅ The following is correct and second action is triggered after next render
    handleChange={() => {
      append({ test: 'test' });
    }}
    
    React.useEffect(() => {
      if (fields.length === 2) {
        remove(0);
      }
    }, [fields])
                
  • It's important to apply ref={register()} instead of ref={register} when working with useFieldArray so register will get invoked during map.

  • When watch-ing the entire Field Array, it's important to supply a default value with fields to avoid empty values from getting returned. eg: watch('fieldArray', fields)

  • When all inputs are removed from the Field Array, watch will return defaultValues. You can use fields.length to avoid this behaviour. eg fields.length ? watch('fieldArray', fields) : []

NameTypeDescription
fieldsobject & { id: string }This object contains the defaultValue and key for all your inputs. It's important to assign defaultValue to the inputs.

Important: Because each input can be uncontrolled, id is required with mapped components to help React to identify which items have changed, are added, or are removed.

{fields.map((data, index) =>
  <input
    key={data.id}
    defaultValue={`data[${index}].value`}
    name={`data[${index}].value`}
  />;
);}
append(obj: object | object[], shouldFocus: boolean = true) => voidAppend input/inputs to the end of your fields and focus.
prepend(obj: object | object[], shouldFocus: boolean = true) => voidPrepend input/inputs to the start of your fields and focus.
insert(index: number, value: object | object[], shouldFocus: boolean = true) => voidInsert input/inputs at particular position and focus.
swap(from: number, to: number) => voidSwap input/inputs position.
move(from: number, to: number) => voidMove input/inputs to another position.
remove(index?: number | number[]) => voidRemove input/inputs at particular position, or remove all when no index provided.
import React from "react";
import { useForm, useFieldArray } from "react-hook-form";

function App() {
  const { register, control, handleSubmit, reset, trigger, setError } = useForm({
    // defaultValues: {}; you can populate the fields by this attribute 
  });
  const { fields, append, prepend, remove, swap, move, insert } = useFieldArray({
    control,
    name: "test"
  });
  
  return (
    <form onSubmit={handleSubmit(data => console.log(data))}>
      <ul>
        {fields.map((item, index) => (
          <li key={item.id}>
            <input
              name={`test[${index}].firstName`}
              ref={register()}
              defaultValue={item.firstName} // make sure to set up defaultValue
            />
            <Controller
              as={<input />}
              name={`test[${index}].lastName`}
              control={control}
              defaultValue={item.lastName} // make sure to set up defaultValue
            />
            <button type="button" onClick={() => remove(index)}>Delete</button>
          </li>
        ))}
      </ul>
      <button
        type="button"
        onClick={() => append({ firstName: "appendBill", lastName: "appendLuo" })}
      >
        append
      </button>
      <input type="submit" />
    </form>
  );
}
import React from 'react';
import { useForm, useWatch, useFieldArray, Control } from 'react-hook-form';

const ConditionField = ({
  control,
  index,
}: {
  control: Control;
  index: number;
}) => {
  const output = useWatch({
    name: 'data',
    control,
    defaultValue: 'yay! I am watching you :)',
  });

  return (
    <>
      {/* Required shouldUnregister: false */}
      {output[index]?.name === "bill" && (
        <input ref={control.register()} name={`data[${index}].conditional`} />
      )}
      {/* doesn't required shouldUnregister: false */}
      <input
        name={`data[${index}].easyConditional`}
        style={{ display: output[index]?.name === "bill" ? "block" : "none" }}
      />
    </>
  );
};

const UseFieldArrayUnregister: React.FC = () => {
  const { control, handleSubmit, register } = useForm<{
    data: { name: string }[];
  }>({
    defaultValues: {
      data: [{ name: 'test' }, { name: 'test1' }, { name: 'test2' }],
    },
    mode: 'onSubmit',
    shouldUnregister: false,
  });
  const { fields } = useFieldArray<{ name: string }>({
    control,
    name: 'data',
  });
  const onSubmit = (data: any) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {fields.map((data, index) => (
        <>
          <input
            name={`data[${index}].name`}
            defaultValue={data.name}
            ref={register()}
          />
          <ConditionField control={control} index={index} />
        </>
      ))}
      <input type="submit" />
    </form>
  );
};

Advanced Usage

Learn how to build complex and accessible forms

Edit