Skip to content

API

focuses on providing the best DX by simplifying the API.

useForm: Function

By invoking useForm, you will receive the following methods register, unregister, errors, watch, handleSubmit, reset, setError, clearError, setValue, getValues, triggerValidation, control and formState.

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

const { register } = useForm({
  mode: 'onSubmit',
  reValidateMode: 'onChange',
  defaultValues: {},
  validationSchema: undefined, // Note: will be deprecated in the next major version with validationResolver
  validationResolver: undefined,
  validationContext: undefined,
  validateCriteriaMode: "firstErrorDetected",
  submitFocusError: true,
  nativeValidation: false, // Note: version 3 only
})
mode: string = 'onSubmit'React Native: only compatible with by using 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 performances.
defaultValues: Record<string, any> = {}React Native: Custom register or using Controller

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 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 manual register field does not provide the ref to React Hook Form.

CodeSandbox
const { register } = useForm({
  defaultValues: {
    firstName: "bill",
    lastName: "luo",
    email: "bluebill1049@hotmail.com",
    pets: [ 'dog', 'cat' ]
  }
})

<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
validationSchema:
Object

Apply form validation rules with Yup at the schema level, please refer to the section.

CodeSandbox
validationResolver:
Function

This callback function allows you to run through any schema or custom validation. The function has the entire form values as argument, and you will need to validate the result and return both values and errors. Read more at section.

Note: This function will be cached inside the hook, you will have to either move the function outside of the component or memorise the function.

validationContext:
Object

This context object will be injected into validationResolver's second argument or Yup validation's context object.

validateCriteriaMode:
firstErrorDetected | all

When set to firstErrorDetected (default), only first error from each field will be gathered.

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

CodeSandbox
reValidateMode:
onChange | onBlur | onSubmit

This option allows you to configure when inputs with errors get re-validated (by default, validation is triggered during an input change.) React Native: only compatible with by using Controller

submitFocusError:
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 a ref will work. Manually registered inputs won't work. eg: register({ name: 'test' }) // doesn't work

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

This method allows you to register input/select Ref and 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. Example table is below:

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 arrays/array fields, you can assign an input name as name[index]. Check out the Field Array example.

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.

By using a custom register call, you will need to update the input value with , because input is no longer registered with its ref.

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

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. This is useful when you used a custom register in useEffect and want to unregister it when the component unmounts.

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

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

export default function App() {
  const { register, handleSubmit, unregister } = useForm();
  const onSubmit = (data) => console.log(data);
  
  useEffect(() => {
    register({ name: 'customRegister' }, { required: true });
    
    return () => unregister('customRegister'); // unregister input after component unmount
  }, [register])

  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.

Note: Difference between V3 and V4:

  • V4: Nested objects

    Reason: optional chaining is getting widely adopted and allows better support for types..

    errors?.yourDetail?.firstName;

  • V3: Flatten object

    Reason: simple and easy to access error.

    errors['yourDetail.firstName'];

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 properly.
messagestringIf 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.
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 &&
        errors.multipleErrorInput.type === "required" &&
        "Your input is required"}
      {errors.multipleErrorInput &&
        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 && errors.errorMessage.message}
      <input type="submit" />
    </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
    validateCriteriaMode: "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 && errors.password.types.required && (
        <p>password required</p>
      )}
      {errors.password && errors.password.types.minLength && (
        <p>password minLength 10</p>
      )}
      {errors.password && errors.password.types.pattern && (
        <p>password number only</p>
      )}

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

watch: (names?: string | string[] | { nest : boolean }) => any

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 to return value.

  • 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 }
{ nest: boolean }Watch all inputs and return nested objectwatch({ nest: true }){ [key:string] : any }
CodeSandbox
import React from 'react';
import { useForm } from 'react-hook-form';

export default function App(props) {
  const { register, watch } = useForm();
  const watchYes = watch('yes', props.yes); // supply default value as second argument
  const watchAllFields = watch(); // watching every fields in the form
  const watchFields = watch(['yes', 'number']); // target specific fields by their names
  // watch array fields by the key, pet[0] and pet[1] will both be watched and returns values
  const pets = watch('pet'); 
  
  return (
    <form>
      <input name="textInput" ref={register({ required: true, maxLength: 50 })} />
      <input type="checkbox" name="yes" ref={register} />
      <input name="pet[0]" ref={register} />
      <input name="pet[1]" ref={register} />
      
      {watchYes && <input type="number" name="numberInput" ref={register({ min: 50 })} />}
      {/* based on yes selection to display numberInput */}
    </form>
  );
}

handleSubmit: (data: Object, 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('Submit event', e)
    alert(JSON.stringify(data))
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register} />
      <input name="lastName" ref={register} />
      <button type="submit">Submit</button>
    </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
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register({ required: true })} />
      <input name="lastName" ref={register} />
      <input type="submit" />
      <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>
  );
}
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>
  );
}
CodeSandbox
import React, { useEffect } from "react";
import { useForm } from "./src";

const defaultValues = {
  firstName: "bill",
  lastName: "luo"
};

export default function App() {
  const { register, reset, watch, setValue } = useForm({
    defaultValues
  });
  const values = watch();

  useEffect(() => {
    register({ name: "firstName" }, { required: true });
    register({ name: "lastName" });
  }, [register]);

  return (
    <form>
      <input
        name="firstName"
        onChange={e => setValue("firstName", e.target.value)}
        value={values.firstName}
        placeholder="First Name"
      />
      <input
        name="lastName"
        onChange={e => setValue("lastName", e.target.value)}
        value={values.lastName}
        placeholder="Last Name"
      />
      <button
        type="button"
        onClick={() => {
          reset(defaultValues);
        }}
      >
        Reset
      </button>
    </form>
  );
}

setError:
(name: string | ManualFieldError[], type?: string | Object, message?: string) => void

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

Note: This method will not persist the error and block the submit action. It's more useful during handleSubmit function when you want to give error feedback to the users after async validation.

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

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

  return (
    <form>
      <input
        name="username"
        onChange={e => {
          const value = e.target.value
          // this will clear error by only pass the name of field
          if (value === "bill") return clearError("username")
          // set an error with type and message
          setError("username", "notMatch", "please choose a different username")
        }}
        ref={register}
      />
      {errors.username && errors.username.message}
    </form>
  )
}
CodeSandbox
import React from "react";
import { useForm } from "react-hook-form";

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

  return (
    <form>
      <input name="username" ref={register} />
      {errors.username && errors.username.message}
      
      <input name="lastName" ref={register} />
      {errors.lastName && errors.lastName.message}
      
      <button type="button" onClick={() => {
        setError([
          {
            type: 'required',
            name: 'lastName',
            message: 'This is required.',
          },
          {
            type: 'minLength',
            name: 'username',
            message: 'Minlength is 10',
          },
        ]);
      }}>
        Set Errors for a single field
      </button>
    </form>
  )
}
CodeSandbox
import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, setError } = useForm({
    validateCriteriaMode: "all" // you will need to enable validate all criteria mode
  });

  return (
    <form>
      <input name="username" ref={register} />
      {errors.username && errors.username.types && (
        <p>{errors.username.types.required}</p>
      )}
      {errors.username && errors.username.types && (
        <p>{errors.username.types.minLength}</p>
      )}

      <button
        type="button"
        onClick={() =>
          setError("username", {
            required: "This is required",
            minLength: "This is minLength"
          })
        }
      >
        Trigger
      </button>
    </form>
  );
}

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

  • undefined: reset all errors

  • string: reset single error

  • string[]: reset multiple errors

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

export default () => {
  const { clearError, errors, register } = useForm();

  return (
    <form>
      <input name="firstName" ref={register({ required: true })} />
      {errors.firstName && "This is required"}
      <input name="lastName" ref={register({ required: true })} />
      {errors.lastName && "This is required"}

      <button type="button" onClick={() => clearError("firstName")}>
        Clear
      </button>
      <button
        type="button"
        onClick={() => clearError(["firstName", "lastName"])}
      >
        Clear Multiple
      </button>
      <button type="button" onClick={() => clearError()}>
        Clear All
      </button>
    </form>
  );
};

setValue:

(name: string, value: any, shouldValidate?: boolean) => void
(Record<Name, any>[], shouldValidate?: boolean) => 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

  • When setValue is invoked and formState touched is updated

Note: By invoking this method, formState will set the input to touched.

You can also set the shouldValidate parameter to truein order to trigger a field validation. eg: setValue('name', 'value', true)

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

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

  return (
    <form>
      <input name="test" ref={register} />
      <input name="test1" ref={register} />
      <input name="object.firstName" ref={register} />
      <input name="array[0].firstName" ref={register} />
      <button type="button" onClick={() => {
        // manually set the 'test' field with value 'bill'
        setValue('test', 'bill')
        
        // set multiple values
        setValue([
          { test : '1', },
          { test1 : '2', },
        ])
        
        // set value as object or array
        setValue('object', { firstName: 'test' })
        setValue('array', [{ firstName: 'test' }])
      }}>SetValue</button>
    </form>
  )
}

getValues: (payload?: { nest: boolean }) => Object

This function will return the entire form data, and it's useful when you want to retrieve form values.

  • By default getValues() will return form data in a flat structure. eg: { test: 'data', test1: 'data1'}

  • Working on the defined form fields, getValues({ nest: true }) will return data in a nested structure according to input name. eg: { test: [1, 2], test1: { data: '23' } }

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()
          // you can run auto save function here eg: autoSave(values)
        }}
      >
        Get Values
      </button>
    </form>
  );
}

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

To manually trigger an input/select validation in the form.

Note: When validation fails, the errors object will be updated.

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

export default function App() {
  const { register, triggerValidation, errors } = useForm();
  console.log(errors);

  return (
    <form>
      <input name="firstName" ref={register({ required: true })} />
      <input name="lastName" ref={register({ required: true })} />
      <button type="button" onClick={() => { triggerValidation("lastName"); }}>Trigger</button>
      <button type="button" onClick={() => { triggerValidation(["firstName", "lastName"]); }}>Trigger Multiple</button>
      <button type="button" onClick={() => { triggerValidation(); }}>Trigger All</button>
      <button
        type="button"
        onClick={async () => {
          const result = await triggerValidation("lastName");
          if (result) { console.log('valid input') }
        }}
      >
        Trigger with 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>
  );
}

formState: Object

This object contain 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.

NameTypeDescription
dirtybooleanSet to true after a user interacted with any of the inputs.
dirtyFieldsSetA unique set of user modified fields.
isSubmittedbooleanSet true after a user submitted the form.
touchedobjectAn object containing all the inputs the user has interacted with.
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 error.

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

CodeSandbox

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.

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 name prop will be used mainly to access the value through the form later.

If you specify a defaultValue prop, it will take priority over the default value specified in useForm's defaultValuesfor this input.

NameTypeRequiredDescription
namestringUnique name of your input.
asReact.ElementType | stringControlled component. eg: as="input", as={<TextInput />} or as={TextInput}.
controlObjectcontrol object is from invoking useForm. it's optional if you are using FormContext.
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: when provided, this take priority over useForm defaultValues for given key.

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 according to register.
onChange(args: any | EventTarget) => anyThis prop allows you to customize the return value, make sure you aware the shape of the external component value props. value or checked attribute will be read when payload's shape is an object which contains type attribute.
onChange={{([ event ]) => event.target.value}}
onChange={{([ { checked } ]) => ({ checked })}}
onChangeNamestringThis prop allows you to target a specific event name for onChange, eg: when onChange event is named onTextChange
onBlurNamestringThis prop allows you to target a specific event name for onBlur, eg: when onBlur event is named onTextBlur
valueNamestringThis prop allows you to support inputs that doesn't use a prop called value. eg: checked, selected and etc.
CodeSandbox
import React from 'react';
import Select from 'react-select';
import { TextField } from "@material-ui/core";
import { useForm, Controller } from 'react-hook-form';

const options = [
  { value: 'chocolate', label: 'Chocolate' },
  { value: 'strawberry', label: 'Strawberry' },
  { value: 'vanilla', label: 'Vanilla' },
];

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 possibility, any potential props passed to <Checkbox/> will be overrided. SomeName => Checkbox *}
      <Controller
        as={<Select options={options} />}
        control={control}
        rules={{ required: true }}
        onChange={([selected]) => {
          // Place your logic here
          return selected;
        }}
        name="reactSelect"
        defaultValue={{ value: 'chocolate' }}
      />
      
      <button>submit</button>
    </form>
  );
}
CodeSandbox
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 => Alert.alert(
    "Form Data",
    JSON.stringify(data),
  );

  return (
    <View>
      <Text>First name</Text>
      <Controller
        as={TextInput}
        control={control}
        name="firstName"
        onChange={args => args[0].nativeEvent.text}
        rules={{ required: true }}
        defaultValue=""
      />
      {errors.firstName && <Text>This is required.</Text>}

      <Text>Last name</Text>
      <Controller
        as={TextInput}
        control={control}
        name="lastName"
        onChange={args => args[0].nativeEvent.text}
        defaultValue=""
      />


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

ErrorMessage: Component

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

NameTypeRequiredDescription
namestringassociated field name.
errorsobjecterrors object from React Hook Form. It's optional if you are using FormContext.
messagestringinline error message.
asReact.ElementType | stringWrapper component or HTML tag. eg: as="span" or as={<Text />}
children({ message: string, messages?: string[]}) => anyThis is a render prop for rendering error message or messages.

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

CodeSandbox
import React from "react";
import { useForm, ErrorMessage } 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: '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>
  );
}
CodeSandbox
import React from "react";
import { useForm, ErrorMessage } from "react-hook-form";

export default function App() {
  const { register, errors, handleSubmit } = useForm({
    validateCriteriaMode: "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.

Note: invoking useFormContext will give you all of the useForm hook functions.

const methods = useFormContext() // methods contain all useForm functions
CodeSandbox
import React from "react"
import { useForm, FormContext, useFormContext } from "react-hook-form"

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

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

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

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.

This hook provides the following object and functions.

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.

  • set defaultValue when you want to set default value or reset with inputs.

  • 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 useFormContext so register will get invoked during map.

  • It doesn't work with custom register at useEffect.

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 | object[]) => voidAppend input/inputs to the end of your fields
prepend(obj: object | object[]) => voidPrepend input/inputs to the start of your fields
insert(index: number, value: object) => voidInsert input/inputs at particular position.
swap(from: number, to: number) => voidSwap input/inputs position.
move(from: number, to: number) => voidMove input/inputs to another position.

Note: difference between move and swap, keep calling move will push input/inputs in a circle, while swap only change two input/inputs' position.

remove(index?: number | number[]) => voidRemove input/inputs at particular position, or remove all when no index is provided.
CodeSandbox
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}>
            <input name={`test[${index}].name`} ref={register} /> 
            <button onClick={() => remove(index)}>Delete</button>
          </li>
        ))}
      </ul>
      <section>
        <button type="button" onClick={() => append({ name: "test" })} >
          append
        </button>
        <button type="button" onClick={() => prepend({ name: "test1" })}>
          prepend
        </button>
      </section>
    </form>
  );
}

validationResolver: (values: any, validationContext?: object) => object

This function allow you to run any external validation methods, such as 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.

Note: make sure you are returning object which contains values and errors, and their default value should be {}.

Note: returning errors object's key should be relevant to your inputs.

Note: this function will be cached inside the custom hook similar as validationSchema, while validationContext is a mutable object which can be changed on each re-render.

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

CodeSandbox
import React from "react";
import { useForm } from "react-hook-form";
import Joi from "@hapi/joi";

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

const resolver = (data: any, validationContext) => {
  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
          };
        }, {})
      : {}
  };
};

export default function App() {
  const { register, handleSubmit, errors } = useForm({
    validationResolver: resolver,
    validationContext: { test: "test" }
  });

  return (
    <form onSubmit={handleSubmit(d => console.log(d))}>
      <input type="text" name="username" ref={register} />
      <input type="submit" />
    </form>
  );
}

validationSchema: Object

If you would like to centralize your validation rules as an external validation schema, you can use the validationSchema parameter. React Hook Form currently supports Yup for object schema validation.

CodeSandbox
import React from 'react'
import ReactDOM from 'react-dom'
import { useForm } from 'react-hook-form'
import * as yup from 'yup'

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

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

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

Browser built-in validation (V3 only)

The following example demonstrates how you can leverage the browser's validation. You only need to set nativeValidation to true and the rest of the syntax is the same as standard validation.

Note: This feature has been removed in V4 due to low usage, but you can still use it in V3

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

export default function App() {
  const { register, handleSubmit } = useForm({ nativeValidation: true });
  const onSubmit = async data => { alert(JSON.stringify(data)); };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        name="firstName"
        ref={register({ required: "Please enter your first name." })} // custom message
      />
      <input name="lastName" ref={register({ required: true })} />
      <input type="submit" />
    </form>
  );
}

Advanced Usage

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