Skip to content

API.V6

focuses on providing the best DX by simplifying the API.

useForm: Function

useForm also has optional arguments. The following example demonstrates all options' default value.


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

const { register } = useForm<FormInputs>({
  mode: 'onSubmit',
  reValidateMode: 'onChange',
  defaultValues: {},
  resolver: undefined,
  context: undefined,
  criteriaMode: "firstErrorDetected",
  shouldFocusError: true,
  shouldUnregister: true,
})
mode: onChange | onBlur | onSubmit = 'onSubmit' | allReact Native: only 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.
allstringValidation will trigger on the blur and change events. Warning: as with the onChange mode, all can have a significant impact on performance.
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 triggered during an input change.)

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

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

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

Note: Values defined in defaultValues will be injected into as defaultValue.

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

CodeSandbox
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) => { values: object, errors: object }

This function allow you to run any external validation methods, such as Yup, Joi, Superstruct and etc. In fact, the goal is not only limited Yup as our external (schema) validation library. We would like to support many other validation libraries to work with React Hook Form. You can even write your custom validation logic to validate.

We support Yup, Joi and Superstruct officially as standard resolvers.

npm install @hookform/resolvers

Notes on building custom resolver:

  • Make sure you are returning object which contains values and errors, and their default value should be {}.

  • Returning errors object's key should be relevant to your inputs.

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

  • Re-validate input will only occur one field at time during user’s interaction, the lib itself will evaluate the error object to trigger re-render accordingly.

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

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

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

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

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

const schema = yup.object().shape({
  name: yup.string().required(),
  age: yup.number().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 name="name" ref={register} />
      <input name="age" type="number" ref={register} />
      <input type="submit" />
    </form>
  );
};
CodeSandbox
import React from 'react';
import { useForm } from 'react-hook-form';
import { joiResolver } from '@hookform/resolvers';
import Joi from "@hapi/joi";

const schema = Joi.object({
  username: Joi.string().required()
});

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

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

interface IFormInput {
  username: string;
}

const schema = Joi.object({
  username: Joi.string().required()
});

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

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>username</label>
      <input name="username" ref={register} />
      <input type="submit" />
    </form>
  );
}
import React from 'react';
import { useForm } from 'react-hook-form';
import { superstructResolver } from '@hookform/resolvers';
import { struct } from 'superstruct';

const schema = struct({
  name: 'string',
  age: 'number',
});

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

  return (
    <form onSubmit={handleSubmit(d => console.log(d))}>
      <label>Test</label>
      <input name="name" ref={register} />
      <input name="age" type="number" ref={register} />
      <input type="submit" />
    </form>
  );
};
CodeSandbox
import * as React from "react";
import { useForm } from "react-hook-form";
import * as Joi from "@hapi/joi";

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

const App = () => {
  const { register, handleSubmit, errors } = useForm({
    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 => {
    console.log(data)
  };

  return (
    <div className="App">
      <h1>resolver</h1>
      
      <form onSubmit={handleSubmit(onSubmit)}>
        <label>Username</label>
        <input type="text" name="username" ref={register} />
        {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 "@hapi/joi";

interface IFormInputs {
  username: string;
}

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

const App = () => {
  const { register, handleSubmit, 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 type="text" name="username" ref={register} />
        {errors.username && <p>errors.username.message</p>}
        <input type="submit" />
      </form>
    </div>
  );
};
shouldUnregister: boolean = trueReact Native: Custom register or using Controller

By default, when inputs gets removed, React Hook Form use MutationObserver to detect and unregister those inputs which gets unmounted. However, you can set shouldUnregister to false to prevent input state from loss due to unmount.

CodeSandbox
shouldUnregistertruefalse
Can you unregister an input?
Value remained when input unmount?
Is form state gets updated?
eg: errors, dirty, 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.

criteriaMode
firstError | all
  • When set to firstError (default), only 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({ name: 'test' }) // doesn't work

Note: focus error input order based on register order.

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

This method allows you to register 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. 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="firstName[0]"{ firstName: [ 'value' ] }
name="name.firstName"{ name: { firstName: 'value' } }
name="name.firstName[0]"{ name: { firstName: [ 'value' ] } }

If you 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 actually the case when you are working with React Native or custom component like react-select. We do provide a component to take care this process for you.

By using custom register, you will need to update the input value with .

register({ name: '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: multiple radio inputs with the same name, you want to register the validation to the last input so the hook understand 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"
  ref={
    register({
      max: 3
    })
  }
/>
min
number
The minimum value to accept for this input.
<input
  name="test"
  ref={
    register({
      min: 3
    })
  }
/>
pattern
RegExp
The regex pattern for the input.
<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)
// callback function
<input
  name="test"
  ref={
    register({
      validate: value => value === '1'
    })
  }
/>
// object of callback functions
<input
  name="test1"
  ref={
    register({
      validate: {
        positive: value => parseInt(value, 10) > 0,
        lessThanTen: value => parseInt(value, 10) < 10
      }
    })
  }
/>
// you can do asynchronous validation as well
<input
  name="test2"
  ref={
    register({
      validate: async value => await fetch(url)
    })
  }
/>

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

This method will allow 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.

CodeSandbox
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 type="text" name="firstName" ref={register} />
      <input type="text" 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 type="text" name="firstName" ref={register} />
      <input type="text" 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 input.

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: that you need to set validateCriteriaMode 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.RefObjectReference for your input element.

Note: You can use to help handle your error states

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

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: true })} />
      {errors.singleErrorInput && "Your input is required"}

      {/* refer to the type of error to display message accordingly */}
      <input
        name="multipleErrorInput"
        ref={register({ 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" name="numberInput" ref={register({ min: 50 })} />
      {errors.numberInput && "Your input required to be more than 50"}

      {/* register with validation and error message */}
      <input
        name="errorMessage"
        ref={register({ 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, errors, handleSubmit } = useForm<IFormInputs>();

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

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

      <label>Error with type check</label>
      <input
        type="text"
        name="multipleErrorInput"
        ref={register({ 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" name="numberInput" ref={register({ 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 { 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 />;
}
CodeSandbox
import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, errors, handleSubmit } = useForm({
    // by setting validateCriteriaMode 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"
        name="password"
        ref={register({ 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 React from "react";
import { useForm } from "react-hook-form";

interface IFormInputs {
  password: string
}

export default function App() {
  const { register, errors, handleSubmit } = useForm<IFormInputs>({
    // by setting validateCriteriaMode 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"
        name="password"
        ref={register({ 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.

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

TypeDescriptionExampleReturn
stringWatch input value by name (similar to lodash get function)watch('inputName')
watch('inputName', 'value')
string | string[] | { [key:string] : any } | undefined
string[]Watch multiple inputswatch(['inputName1'])
watch(['field1'], { field1: '1' })
{ [key:string] : any }
undefinedWatch all inputswatch()
watch(undefined, { field: 'value1' })
{ [key:string] : any }
CodeSandbox
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="text"
          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";

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
          type="text"
          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 />;
}

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

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))

CodeSandbox
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);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <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>
  );
}

reset: (values?: Record<string, any>, omitResetState: OmitResetState = {}) => void

This function will reset the fields' values and errors within the form. By supply omitResetState, you have the freedom to only reset specific piece of state. You can pass values as an optional argument to reset your form into assigned default values.

Note: For controlled components like React-Select which don't expose ref, you will have to reset the input value manually through or using to wrap around your controlled component.

Note: You will need to supply defaultValues at useForm to reset Controller components' value.

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

export default function App() {
  const { register, handleSubmit, reset } = useForm();
  const onSubmit = (data, e) => {
    // e.target.reset(); // HTML standard reset() function will only reset inputs' value
  };
  
  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} />
      <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
          dirty: 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 type="text" name="firstName" ref={register({ required: true })} />

      <label>Last name</label>
      <input type="text" 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>
  );
}
CodeSandbox
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>
  );
}

setError:(name: string, error: { type: string, types: object, message?: string }) => 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.

  • Set an error which doesn't associated with an input field and will be persisted. This error must be removed manually with clearError.

  • It's useful during handleSubmit function when you want to give error feedback to the users after async validation.

CodeSandbox
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"
        type="text"
        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)}>
      <label>Username</label>
      <input
        name="username"
        type="text"
        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>
  );
};
CodeSandbox
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"
        type="text"
        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" type="text" 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"
        type="text"
        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" type="text" 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>
  );
};
CodeSandbox
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>Last Name</label>
      <input
        name="lastName"
        type="text"
        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>();
  
  const onSubmit = (data: FormInputs) => {
    console.log(data)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>Last Name</label>
      <input
        name="lastName"
        type="text"
        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 single error

  • string[]: reset multiple errors

CodeSandbox
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" type="text" ref={register({ required: true })} />
      <input name="lastName" type="text" ref={register({ required: true })} />
      <input name="username" type="text" 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" type="text" ref={register({ required: true })} />
      <input name="lastName" type="text" ref={register({ required: true })} />
      <input name="username" type="text" 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, shouldValidate?: boolean, config: Object) => void

This function allows you to dynamically set registered input/select value. At the same time, it tries to avoid re-rendering when it's not necessary. 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 dirty 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 })
CodeSandbox
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" type="text" ref={register} />
      <input name="lastName" type="text" ref={register} />
      <button onClick={() => setValue("firstName", "Grace")}>
        Set First Name Value
      </button>
      <button
        onClick={() =>
          setValue("lastName", "Hopper", {
            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" type="text" ref={register} />
      <input name="lastName" type="text" ref={register} />
      <button onClick={() => setValue("firstName", "Grace")}>
        Set First Name Value
      </button>
      <button
        onClick={() =>
          setValue("lastName", "Hopper", {
            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");
  // ❌: type error
  setValue([{ string: "test" }]);
  // function setValue<"string">(namesWithValue: DeepPartial<Pick<FormValues, "string">>[], shouldValidate?: boolean | undefined): void
  setValue([{ number: "error" }]);
  // ❌: type error
  setValue([{
    string: "test",
    number: 1
  }]);
  // function setValue<"string" | "number">(namesWithValue: DeepPartial<Pick<FormValues, "string" | "number">>[], shouldValidate?: boolean | undefined): void
  setValue([
    { string: "test" },
    { number: 1 },
  ]);
  // function setValue<"string" | "number">(namesWithValue: DeepPartial<Pick<FormValues, "string" | "number">>[], shouldValidate?: boolean | undefined): void
  setValue("object", { number: 1 });
  // function setValue<"object", { number: number }>(name: "object", value: DeepPartial<{ number: number; boolean: boolean }>, shouldValidate?: boolean | undefined): void
  setValue("object.boolean", true);
  // function setValue<"object.boolean", boolean>(name: "object.boolean", value: boolean, shouldValidate?: boolean | undefined): void
  setValue("array", [{ string: "test" }]);
  // function setValue<"array", { string: string; }[]>(name: "array", value?: (DeepPartial<{ string: string; boolean: boolean; }> | undefined)[] | undefined, shouldValidate?: boolean | undefined): void
  setValue("array[1].boolean", true);
  // function setValue<"array[1].boolean", boolean>(name: "array[1].boolean", value: boolean, shouldValidate?: boolean | undefined): void
  setValue("array[1].boolean", "noerror");
  // function setValue<"array[1].boolean", string>(name: "array[1].boolean", value: string, shouldValidate?: boolean | undefined): void
  setValue<string, boolean>("array[1].boolean", "error");
  // ❌: type error
  setValue([{ array: [{ boolean: false }]}]);
  // function setValue<"array">(namesWithValue: DeepPartial<Pick<FormValues, "array">>[], shouldValidate?: boolean | undefined): void
  
  return <form />;
}

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

This function will help you to read form values. The difference between watch is getValues will not trigger re-render or subscribed to input changes. The functions covers:

  • getValues(): Read entire form values.

  • getValues('test'): Read individual input value byname.

  • getValues(['test', 'test1']): Read multiple inputs bynames.

CodeSandbox
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 singleValue = 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 singleValue = 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>

To manually trigger validation in the form.

CodeSandbox
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 is made for React Hook Form's Controller component, which contains methods for registering controlled component into React Hook Form.

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

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        as={TextField}
        name="firstName"
        control={control}
        defaultValue=""
      />
      
      <input type="submit" />
    </form>
  );
}
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.

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. This reduced re-render feature only applies to the Web platform due to a lack of support for Proxy in React Native.

NameTypeDescription
isDirtybooleanSet to true after the user modified any of the inputs.

Note: File typed input will need to manage at app level due to the ability to cancel file selection and FileList object.

dirtyFieldsobjectAn object with the user-modified fields.
touchedobjectAn object containing all the inputs the user has interacted with.
isSubmittedbooleanSet to true after the user submitted the form. Iits state will remain submitted until the reset method is invoked.
isSubmittingbooleantrue if the form is currently being submitted. false 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.

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

export default function App() {
  const { register, handleSubmit, errors, formState } = useForm();
  const onSubmit = data => console.log(data);
  
  // Read the formState before render to subscribe the form state through Proxy
  const { dirty, isSubmitting, touched, submitCount } = formState;
  
  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 Material-UI. 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. it's optional if you are using FormContext.
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.

<Controller 
  as={<TextInput />} 
  control={control} 
  name="test" 
/>
renderFunctionThis is a render prop. A function that returns a React element and provide the ability to attach events and value into the component. This make it easy to integrate with external controlled component with non-standard props name: onChange, onBlur and value.
<Controller
  control={control} 
  name="test" 
  render(({ onChange, onBlur, value }) => (
    <Input 
      onTextChange={onChange} 
      onTextBlur={onBlur} 
      textValue={value} 
    />
  ))
/>
<Controller render={props => <Input {...props} />} />
defaultValueanyThe same as uncontrolled component's defaultValue, when supply boolean value, it will be treated as checkbox input.

Note: you will need to supply either defaultValue or defaultValues at useForm

Note: if your form will invoke reset with default values, you will need to provide defaultValues at useForm level instead of set inline defaultValue.

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.

CodeSandbox
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))}>
      {* // Preferred syntax on most cases. If you need props, pass TextField props to Controller props (forwarded) *}
      <Controller as={TextField} name="TextField" control={control} defaultValue="" />
      
      {* // Another approach is using render props to customise event and value *}
      <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))}>
      {* // Preferred syntax on most cases. If you need props, pass TextField props to Controller props (forwarded) *}
      <Controller as={TextField} name="TextField" control={control} defaultValue="" />
      
      {* // Another approach is using render props to customise event and value *}
      <Controller
        control={control}
        name="ReactDatepicker"
        render={({ onChange, onBlur, value}) => (
          <ReactDatePicker
            onChange={onChange}
            onBlur={onBlur}
            selected={value}
          />
        )}
      />
      
      <input type="submit" />
    </form>
  );
}
Expo
import React from "react";
import { Text, View, TextInput, Button, Alert } from "react-native";
import { useForm, Controller } from "react-hook-form";

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

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

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

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

ErrorMessage: Component

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

npm install @hookform/error-message
NameTypeRequiredDescription
namestringassociated field name.
errorsobjecterrors object from React Hook Form. It's optional if you are using FormContext.
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 validateCriteriaMode 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">
        {({ message }) => <p>{message}</p>}
      </ErrorMessage>
      
      <input name="name" ref={register({ required: true })} />
      <ErrorMessage errors={errors} name="name" message="This is required" />
      
      <input type="submit" />
    </form>
  );
}
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({
    criteriaMode "all"
  });
  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        name="multipleErrorInput"
        ref={register({
          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">
        {({ messages }) =>
          messages &&
          Object.entries(messages).map(([type, message]) => (
            <p key={type}>{message}</p>
          ))
        }
      </ErrorMessage>

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

useFormContext: Component

Hook function that 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 FormContext provider component for useFormContext to work properly.

NameTypeDescription
...propsObjectAccept all useForm methods.
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 name="test" ref={register} />;
}
import React from "react";
import { useForm, FormProvider, FormContext, 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() {
  return (
    <FormContext.Consumer>
      {({ register }) => <input name="test" ref={register} />}
    </FormContext.Consumer>
  );
}

useWatch: ({ control?: any, name?: string, defaultValue?: any }) => object

Share the same functionality as watch API, however, this will isolate re-render at your component level and potentially result in better performance for your application.

NameTypeRequiredDescription
namestringassociated field name.
controlObjectcontrol object is from invoking useForm. it's optional if you are using FormContext.
defaultValueanydefault value for `useWatch` to return before the initial render.
CodeSandbox
import React from "react";
import { useForm, useWatch } from "react-hook-form";

function IsolateReRender({ control }) {
  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 <div>{firstName}</div>; // only re-render at the component level, when firstName changes
}

function App() {
  const { register, control, handleSubmit } = useForm();
  
  return (
    <form onSubmit={handleSubmit(data => console.log("data", data))}>
      <input ref={register} name="firstName" />
      <input ref={register} name="last" />
      <IsolateReRender control={control} />
      
      <input type="submit" />
    </form>
  );
}
const none = useWatch<{
  test: string;
  test1: number;
  test2: boolean;
}>({});
// const none: {
//   test?: string | undefined;
//   test1?: number | undefined;
//   test2?: boolean | undefined;
// }

const defaultValueOnly = useWatch<{
  test: string;
  test1: number;
  test2: boolean;
}>({ defaultValue: { test: 'test' } });
// const defaultValueOnly: {
//   test?: string | undefined;
//   test1?: number | undefined;
//   test2?: boolean | undefined;
// }

const nameOnly1 = useWatch<string>({ name: 'test' });
// const nameOnly1: string | undefined

const nameOnly2 = useWatch<number>({ name: 'test1' });
// const nameOnly2: number | undefined

const nameOnly3 = useWatch<boolean>({ name: 'test2' });
// const nameOnly3: boolean | undefined

const namesOnly1 = useWatch<{ test: string }>({ name: ['test'] });
// const namesOnly1: {
//   test?: string | undefined;
// }

const namesOnly2 = useWatch<{
  test: string;
  test1: number;
}>({ name: ['test', 'test1'] });
// const namesOnly2: {
//   test?: string | undefined;
//   test1?: number | undefined;
// }

const namesOnly3 = useWatch<{
  test: string;
  test1: number;
  test2: boolean;
}>({ name: ['test', 'test1', 'test2'] });
// const namesOnly3: {
//   test?: string | undefined;
//   test1?: number | undefined;
//   test2?: boolean | undefined;
// }

const controlOnly = useWatch<{
  test: string;
  test1: number;
  test2: boolean;
}>({ control });
// const controlOnly: {
//   test?: string | undefined;
//   test1?: number | undefined;
//   test2?: boolean | undefined;
// }

const defaultValueAndControl = useWatch<{
  test: string;
  test1: number;
  test2: boolean;
}>({
  defaultValue: { test: 'test' },
  control,
});
// const defaultValueAndControl: {
//   test?: string | undefined;
//   test1?: number | undefined;
//   test2?: boolean | undefined;
// }

const defaultValueAndName1 = useWatch<string>({
  name: 'test',
  defaultValue: 'test',
});
// const defaultValueAndName1: string

const defaultValueAndName2 = useWatch<number>({
  name: 'test1',
  defaultValue: 1,
});
// const defaultValueAndName2: number

const defaultValueAndName3 = useWatch<boolean>({
  name: 'test2',
  defaultValue: true,
});
// const defaultValueAndName3: boolean

const defaultValueAndNames1 = useWatch<{ test: string }>({
  defaultValue: { test: 'test' },
  name: ['test'],
});
// const defaultValueAndNames1: {
//   test?: string | undefined;
// }

const defaultValueAndNames2 = useWatch<{
  test: string;
  test1: number;
}>({
  defaultValue: { test: 'test', test1: 1 },
  name: ['test', 'test1'],
});
// const defaultValueAndNames2: {
//   test?: string | undefined;
//   test1?: number | undefined;
// }

const defaultValueAndNames3 = useWatch<{
  test: string;
  test1: number;
  test2: boolean;
}>({
  defaultValue: { test: 'test', test1: 1, test2: true },
  name: ['test', 'test1', 'test2'],
});
// const defaultValueAndNames3: {
//   test?: string | undefined;
//   test1?: number | undefined;
//   test2?: boolean | undefined;
// }

const nameAndControl = useWatch<number>({ name: 'test1', control });
// const nameAndControl: number | undefined

const namesAndControl = useWatch<{
  test: string;
  test1: number;
}>({ name: ['test', 'test1'], control });
// const namesAndControl: {
//   test?: string | undefined;
//   test1?: number | undefined;
// }

const defaultValueAndNameAndControl1 = useWatch<number>({
  defaultValue: 1,
  name: 'test1',
  control,
});
// const defaultValueAndNameAndControl1: number

const defaultValueAndNamesAndControl2 = useWatch<{
  test: string;
  test1: number;
  test2: boolean;
}>({
  name: ['test', 'test1', 'test2'],
  defaultValue: { test: 'test', test1: 1, test2: true },
  control,
});
// const defaultValueAndNamesAndControl2: {
//   test?: string | undefined;
//   test1?: number | undefined;
//   test2?: boolean | undefined;
// }

// type error
const error1 = useWatch<string>({ control, name: 'test', defaultValue: 1 }); // ❌
const output2 = useWatch<{
  test: string;
  test1: number;
  test2: boolean;
}>({
  control,
  name: ['test', 'test2'],
  defaultValue: { test: 1, test2: true },
}); // ❌

const output3 = useWatch<{
  test: string;
  test1: number;
  test2: boolean;
}>({
  control,
  name: ['test', 'test2'],
  defaultValue: { notExists: 'notExists', test2: true },
}); // ❌

// expect type error, but no type error because support nested object
const expectTypError = useWatch<{
  test: string;
  test1: number;
  test2: boolean;
}>({ control, name: ['test1', 'notExists'] }); // ✅
// const expectTypError: {
//   test?: string | undefined;
//   test1?: number | undefined;
//   test2?: boolean | undefined;
// }

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

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

NameTypeRequiredDescription
namestringassociated field name.
controlObjectcontrol object is from invoking 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) => (
      {/* important: using id from to track item added or removed */}
      <div key={field.id}>
        <input name={`test[${index}]`} ref={register()} />
      </div>
    ))}
  );
}

Important: useFieldArray is built on top of uncontrolled components. The following notes will help you aware and be mindful of its behaviour during implementation.

  • You can populate the fields by supply defaultValues at useForm hook.

  • Make sure you assign id from fields object as your component key.

  • Make sure to set defaultValue to fields[index].

  • 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.

  • It doesn't work with custom register at useEffect or conditional render. For conditional render consider using style to toggle the viability and `validate` function for conditional validation.

NameTypeDescription
fieldsobject & { id: string }This object is the source of truth to map and render inputs.

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

eg: {fields.map(d => <input key={d.id} />)}

append(obj: object, shouldFocus: boolean = true) => voidAppend input/inputs to the end of your fields and focus.
prepend(obj: object, shouldFocus: boolean = true) => voidPrepend input/inputs to the start of your fields and focus.
insert(index: number, value: 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 } = useForm({
    // defaultValues: {}; you can populate the fields by this attribute 
  });
  const { fields, append, prepend, remove } = useFieldArray({
    control,
    name: "test"
  });
  
  return (
    <form onSubmit={handleSubmit(data => console.log("data", data))}>
      <ul>
        {fields.map((item, index) => (
          <li key={item.id}>
            {/* important: useFieldArray only works with ref={register()} */}
            <input name={`test[${index}].name`} defaultValue={item.name} ref={register()} />
            <button onClick={() => remove(index)}>Delete</button>
          </li>
        ))}
      </ul>
      <button type="button" onClick={() => append({ name: "test" })} >
        append
      </button>
      <button type="button" onClick={() => prepend({ name: "test1" })}>
        prepend
      </button>
    </form>
  );
}

Advanced Usage

Learn how to build complex and accessible forms with React Hook Form.