useForm: Function
useForm
also takes optional arguments. The following example demonstrates all of the arguments with their default values.
const { register } = useForm({ mode: 'onSubmit', reValidateMode: 'onChange', defaultValues: {}, resolver: undefined, context: undefined, criteriaMode: "firstError", shouldFocusError: true, shouldUnregister: true, })
type FormInputs = { firstName: string; lastName: string; }; const { register } = useForm<FormInputs>({ mode: 'onSubmit', reValidateMode: 'onChange', defaultValues: {}, resolver: undefined, context: undefined, criteriaMode: "firstError", shouldFocusError: true, shouldUnregister: true, })
mode: onChange | onBlur | onSubmit | all = 'onSubmit'
React Native: only compatible with Controller
Name | Type | Description |
---|---|---|
onSubmit (Default) | string | Validation will trigger on the submit event and invalid inputs will attach onChange event listeners to re-validate them. |
onBlur | string | Validation will trigger on the blur event. |
onChange | string | Validation 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. |
all | string | Validation will trigger on the blur and change events. |
reValidateMode: onChange | onBlur | onSubmit = 'onChange'
React Native: Custom register or using Controller
This option allows you to configure when inputs with errors get re-validated after submit. By default, validation is only triggered during an input change. |
defaultValues: Record<string, any> = {}Video
You can set an 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 the default values for the entire form.
Important:
defaultValues
is cached at the first render within the custom hook. If you want to reset thedefaultValues
, you should use the api.Values defined in
defaultValues
will be injected into asdefaultValue
.It doesn't auto populate with the manually registered input (eg:
register({ name: 'test' })
) because the customregister
field does not provide theref
to React Hook Form.Its not default state for the form, to include additional form values:
Register hidden input:
<input type="hidden" ref={register} name="test" />
Combine values at onSubmit callback.
const { register } = useForm({ defaultValues: { firstName: "bill", lastName: "luo", email: "bluebill1049@hotmail.com", isDeveloper: true } }) <input name="firstName" ref={register} /> // ✅ working version <input name="lastName" ref={() => register({ name: 'lastName' })} /> // ❌ above example does not work with "defaultValues" due to its "ref" not being provided
type Inputs = { firstName: string; lastName: string; email: string; isDeveloper: boolean; } const { register } = useForm<Inputs>({ defaultValues: { firstName: "bill", lastName: "luo", email: "bluebill1049@hotmail.com", isDeveloper: true } }) <input name="firstName" ref={register} /> // ✅ working version <input name="lastName" ref={() => register({ name: 'lastName' })} /> // ❌ above example does not work with "defaultValues" due to its "ref" not being provided
resolver: (values: any, context?: object) => Promise<ResolverResult> | ResolverResult
This function allows you to use any external validation library such as Yup, Joi, Superstruct and many others. Our goal is to make sure you can seamlessly integrate whichever validation library you prefer. If you're not using a library, you can always write your own logic to validate your forms.
At this time, we offer officially supported resolvers for: Yup, Joi and Superstruct.
npm install @hookform/resolvers
Notes on building a custom resolver:
Make sure you are returning an object that has
values
anderrors
properties. Their default values should be{}
.The keys of the
error
object should match thename
value of your fields.This function will be cached inside the custom hook, while
context
is a mutableobject
which can be changed on each re-render.Re-validation of an input will only occur one field at time during a user’s interaction. The lib itself will evaluate the
error
object to trigger a re-render accordingly.
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> ); };
shouldUnregister: boolean = true
CodeSandbox
By default, when an input is removed, React Hook Form uses MutationObserver
to detect and unregister
the input(s) that are unmounted. However, you can set shouldUnregister
to false to maintain the input state even when unmounting occurs.
true | false | |
Can you unregister an input? | ✅ | ❌ |
Value remains when input unmounts? | ❌ | ✅ |
Is form state updated? eg: isValid, isDirty, touched | ✅ | ❌ you will need to clear manually |
object | This context |
firstError | all |
|
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 Note: the focus order is based on the |
♦
register: (Ref, validateRule?) => voidReact Native: Custom register or using Controller
This method allows you to register an input/select Ref
and apply validation rules into React Hook Form.
Validation rules are all based on HTML standard and also allow custom validation.
Important: name
is required and unique (can not start with a number). Input name also supports dot and bracket syntax, which allows you to easily create nested form fields.
Input Name | Submit 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're working on simple Array Fields
, you can assign an input name as name[index]
. Check out the Field Array example. For more advance usage, make sure to checkout useFieldArray
.
Custom Register
You can also register inputs manually, which is useful when working with custom components and Ref
is not accessible. This is common when you are working with React Native or custom components like react-select. To make this easier, we provide a component to take care this process for you.
If you choose to not use Controller
and manually register fields, you will need to update the input value with .
register({ 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: If you have multiple radio inputs with the same name, you need to register the validation to the last input so the hook knows to validate them as a group at the end.
Name | Description | Code Examples |
---|---|---|
ref React.RefObject | React element ref |
|
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. |
|
maxLength
| The maximum length of the value to accept for this input. |
|
minLength
| The minimum length of the value to accept for this input. |
|
max
| The maximum value to accept for this input. |
|
min
| The minimum value to accept for this input. |
|
pattern
| The regex pattern for the input. |
|
validate
| 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) |
|
unregister: (name: string | string[]) => void
This method allows you to unregister
a single input or an array of inputs.
Note: when you unregister an input, its value will no longer be included in the form data that gets submitted.
import React, { useEffect } from "react"; import { useForm } from "react-hook-form"; export default function App() { const { register, handleSubmit, unregister } = useForm(); const onSubmit = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input 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 field.
Name | Type | Description |
---|---|---|
type | string | Error Type. eg: required, min, max, minLength |
types | Record<{ string, string | boolean }> | This is useful when you want to return all validation errors for a single input. For instance, a password field that is required to have a minimum length and contain a special character. Note: You need to set |
message | string | React.ReactElement | If you registered your input with an error message, then it will be put in this field, otherwise it's an empty string by default. |
ref | React.RefObject | Reference for your input element. |
Important: Avoid using error object key names to avoid data overwrite.
eg: register('user'); register('user.type'); ❌ // error's type will get overwritten.
Note: You can use the component to help display your error states
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 />; }
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 ofwatch
will returnundefined
because it is called beforeregister
, but you can set thedefaultValue
as the second argument.If
defaultValues
was initialised inuseForm
as an argument, then the first render will return what's provided indefaultValues
.For
useFieldArray
, when all inputs get removed will returndefaultValues
, you can walk around this issue by checkingfields.length
.
Type | Description | Example | Return |
---|---|---|---|
string | Watch input value by name (similar to lodash get function) | watch('inputName') watch('inputName', 'value') | string | string[] | { [key:string] : any } | undefined |
string[] | Watch multiple inputs | watch(['inputName1']) watch(['field1'], { field1: '1' }) | { [key:string] : any } |
undefined | Watch all inputs | watch() watch(undefined, { field: 'value1' }) | { [key:string] : any } |
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))
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 supplying omitResetState
, you have the freedom to only reset specific parts of the state. You can pass values
as an optional argument to reset your form into the assigned default values.
Note: For controlled components like React-Select
which do not expose a ref
prop, you will have to reset the input value manually with or by wrapping your component with .
Note: You will need to pass defaultValues
to useForm
in order to reset
the Controller
components' value.
Note: if you invoke reset
with empty argument or memorised defaultValues, then you will need to pass ref={register()}
at input instead of ref={register}
. Alternatively, you can reset
the form with values. eg: reset({})
.
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 isDirty: true, // dirty will not be reset isSubmitted: false, touched: false, isValid: false, submitCount: false, }); }} /> </form> ); }
import React from "react"; import { useForm } from "react-hook-form"; interface UseFormInputs { firstName: string lastName: string } export default function Form() { const { register, handleSubmit, reset, errors } = useForm<UseFormInputs>(); const onSubmit = (data: UseFormInputs) => { console.log(data) }; return ( <form onSubmit={handleSubmit(onSubmit)}> <label>First name</label> <input 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> ); }
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.
Allows you to set an error that is not associated with an input field that will be persisted until it is manually cleared with
clearError
.Can be useful in the
handleSubmit
method when you want to give error feedback to a user after async validation. (ex: API returns validation errors)
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> ); };
clearErrors: (name?: string | string[]) => void
undefined
: reset all errorsstring
: reset the error on a single fieldstring[]
: reset errors on the given fields
import * as React from "react"; import { useForm } from "react-hook-form"; const App = () => { const { register, errors, handleSubmit, clearErrors } = useForm(); const onSubmit = data => { console.log(data) }; return ( <form onSubmit={handleSubmit(onSubmit)}> <input name="firstName" 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 the value of a registered
field. At the same time, it tries to avoid unnecessary re-rerenders. Only the following conditions will trigger a re-render:
When an error is triggered by a value update
When an error is corrected by a value update
When setValue is invoked for the first time and
formState.isDirty
is set to true
You can also set the shouldValidate
parameter to true
in order to trigger a field validation.
setValue('name', 'value', { shouldValidate: true })
You can also set the shouldDirty
parameter to true
in order to set field to dirty.
setValue('name', 'value', { shouldDirty: true })
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
An optimized helper for reading form values. The difference between watch
and getValues
is that getValues
will not trigger re-renders or subscribe to input changes.
getValues()
: Read all form values.getValues('test')
: Read an individual field value by name.getValues(['test', 'test1'])
: Read multiple fields by name.
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>
Manually triggers form validation.
trigger()
: Triggers validation on all fields.trigger('test')
: Triggers validation on a specific field value by name.trigger(['test', 'test1'])
: Triggers validation on multiple fields by name.
import React from "react"; import { useForm } from "react-hook-form"; export default function App() { const { register, trigger, errors } = useForm(); return ( <form> <input name="firstName" ref={register({ required: true })} /> <input name="lastName" ref={register({ required: true })} /> <button type="button" onClick={() => { trigger("lastName"); }}>Trigger</button> <button type="button" onClick={() => { trigger(["firstName", "lastName"]); }}>Trigger Multiple</button> <button type="button" onClick={() => { trigger(); }}>Trigger All</button> <button type="button" onClick={async () => { const result = await trigger("lastName"); if (result) { console.log("valid input") } }} > Trigger Result </button> </form> ); }
import React from "react"; import { useForm } from "react-hook-form"; type FormInputs = { firstName: string lastName: string } export default function App() { const { register, trigger, errors } = useForm<FormInputs>(); return ( <form> <input name="firstName" ref={register({ required: true })} /> <input name="lastName" ref={register({ required: true })} /> <button type="button" onClick={() => { trigger("lastName"); }}>Trigger</button> <button type="button" onClick={() => { trigger(["firstName", "lastName"]); }}>Trigger Multiple</button> <button type="button" onClick={() => { trigger(); }}>Trigger All</button> <button type="button" onClick={async () => { const result = await trigger("lastName"); if (result) { console.log("valid input") } }} > Trigger Result </button> </form> ); }
control: Object
This object is made for React Hook Form's Controller component, which contains methods for registering a controlled component into React Hook Form.
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.
Name | Type | Description |
---|---|---|
isDirty | boolean | Set to true after the user modifies 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. |
dirtyFields | object | An object with the user-modified fields. |
touched | object | An object containing all the inputs the user has interacted with. |
isSubmitted | boolean | Set to true after the form is submitted. Will remain true until the reset method is invoked. |
isSubmitting | boolean | true if the form is currently being submitted. false if otherwise. |
submitCount | number | Number of times the form was submitted. |
isValid | boolean | Set to true if the form doesn't have any errors.Note: |
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 { isDirty, isSubmitting, touched, submitCount } = formState; return ( <form onSubmit={handleSubmit(onSubmit)}> <input name="test" ref={register} /> <input type="submit" /> </form> ); }
import React from "react"; import { useForm } from "react-hook-form"; type FormInputs = { test: string } export default function App() { const { register, handleSubmit, errors, formState } = useForm<FormInputs>(); const onSubmit = (data: FormInputs) => console.log(data); // Read the formState before render to subscribe the form state through Proxy const { isDirty, 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.
Name | Type | Required | Description |
---|---|---|---|
name | string | ✓ | Unique name of your input. |
control | Object | control object is from invoking useForm . Optional when using FormContext . | |
render | Function | This is a render prop. A function that returns a React element and provides the ability to attach events and value into the component. This simplifies integrating with external controlled components with non-standard prop names. Provides onChange , onBlur and value to the child component.
| |
as | React.ElementType | Controller will inject Every prop you pass to the Controller component will be forwarded to the component instance you provided with the Note: The following props will be passed into your component:
| |
defaultValue | any | The same as an uncontrolled component's defaultValue . When passing a boolean value, it will be treated as checkbox input.Note: You need to either set Note: if your form will invoke | |
rules | Object | Validation rules in the same format as for register .
| |
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 |
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> ); }
ErrorMessage: Component
A simple component to render associated input's error message.
npm install @hookform/error-message
Name | Type | Required | Description |
---|---|---|---|
name | string | ✓ | Name of the field. |
errors | object | errors object from React Hook Form. Optional if you are using FormContext . | |
message | string | React.ReactElement | Inline error message. | |
as | React.ElementType | string | Wrapper component or HTML tag. eg: as="span" or as={<Text />} | |
render | ({ message: string | React.ReactElement, messages?: Object}) => any | This is a render prop for rendering error message or messages. Note: you need to set |
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'; interface FormInputs { singleErrorInput: string } export default function App() { const { register, errors, handleSubmit } = useForm<FormInputs>(); const onSubmit = (data: FormInputs) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input name="singleErrorInput" ref={register({ required: "This is required." })} /> <ErrorMessage errors={errors} name="singleErrorInput" /> <ErrorMessage errors={errors} name="singleErrorInput"> {({ 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> ); }
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.
Name | Type | Description |
---|---|---|
...props | Object | Accept 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} />; }
useWatch: ({ control?: any, name?: string, defaultValue?: any }) => object
Behaves similarly to the watch
API, however, this will isolate re-rendering at the component level and potentially result in better performance for your application.
Name | Type | Required | Description |
---|---|---|---|
name | string | Name of the field. | |
control | Object | control object is from invoking useForm . it's optional if you are using FormContext. | |
defaultValue | any | default value for `useWatch` to return before the initial render. |
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> ); }
import React from "react"; import { useForm, useWatch } from "react-hook-form"; interface FormInputs { firstName: string } function FirstNameWatched({ control }: { control: Control<FormInputs> }) { const firstName = useWatch({ control, name: "firstName", // without supply name will watch the entire form, or ['firstName', 'lastName'] to watch both defaultValue: "default" // default value before the render }); return <p>Watch: {firstName}</p>; // only re-render at the component level, when firstName changes } function App() { const { register, control, handleSubmit } = useForm<FormInputs>(); const onSubmit = (data: FormInputs) => { console.log(data) }; return ( <form onSubmit={handleSubmit(onSubmit)}> <label>First Name:</label> <input ref={register} name="firstName" /> <input type="submit" /> <FirstNameWatched control={control} /> </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' }) => objectVideo
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.
Name | Type | Required | Description |
---|---|---|---|
name | string | Name of the field. Important: make sure name is in object shape: | |
control | Object | control object is from invoking useForm . it's optional if you are using FormContext. | |
keyName | string = '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 */} <input key={field.id} name={`test[${index}]`} ref={register()} /> ))} ); }
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 supplydefaultValues
atuseForm
hook.Make sure you assign
id
fromfields
object as your component key.Make sure to set
defaultValue
tofields[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 ofref={register}
when working withuseFieldArray
soregister
will get invoked duringmap
.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.When
watch
the entire Field Array, it's important to supply a default value withfields
to avoid empty values gets returned. eg:watch('fieldArray', fields)
When all inputs get removed from the Field Array,
watch
will returndefaultValues
. You can usefields.length
to avoid this behaviour. egfields.length ? watch('fieldArray', fields) : []
Name | Type | Description |
---|---|---|
fields | object & { id: string } | This object is the source of truth to map and render inputs. Important: because each inputs can be uncontrolled, eg: |
append |
| Append input/inputs to the end of your fields and focus. |
prepend |
| Prepend input/inputs to the start of your fields and focus. |
insert |
| Insert input/inputs at particular position and focus. |
swap |
| Swap input/inputs position. |
move |
| Move input/inputs to another position. |
remove |
| Remove input/inputs at particular position, or remove all when no index provided. |
import React from "react"; import { useForm, useFieldArray } from "react-hook-form"; function App() { const { register, control, handleSubmit, reset, trigger, setError } = useForm({ // defaultValues: {}; you can populate the fields by this attribute }); const { fields, append, prepend, remove, swap, move, insert } = useFieldArray({ control, name: "test" }); // trigger validation at the field array level // useEffect(() => { // if (fields) trigger('test'); // with resolver // if (!fields.length) setError('miniLength, 'at least 1 field array item'); // build-in validator // }, [trigger, fields]) return ( <form onSubmit={handleSubmit(data => console.log(data))}> <ul> {fields.map((item, index) => ( <li key={item.id}> <input name={`test[${index}].firstName`} ref={register()} defaultValue={item.firstName} // make sure to set up defaultValue /> <Controller as={<input />} name={`test[${index}].lastName`} control={control} defaultValue={item.lastName} // make sure to set up defaultValue /> <button type="button" onClick={() => remove(index)}>Delete</button> </li> ))} </ul> <button type="button" onClick={() => append({ firstName: "appendBill", lastName: "appendLuo" })} > append </button> <button type="button" onClick={() => prepend({ firstName: "prependFirstName", lastName: "prependLastName" })} > prepend </button> <input type="submit" /> </form> );
Advanced Usage
Learn how to build complex and accessible forms with React Hook Form.