7.2 - React Hook Form
React Hook Form is a library for handling forms in React. It's useful useForm
custom Hook handles form state and validation. React Hook Form integrates well with TypeScript.
Why use React Hook Form?
React Hook Form is a library for handling forms in React. It simplifies things like:
- Validation - Developers can add validation to sure user input data is correctly formatted.
- Handling form state - Rather than manually handling the state of each individual field, with React Hook Forms the entire form shares one state.
- Incorportating other forms libraries - Other libraries, such as those for validation like Zod, can be smoothly integrated with React Hook Forms.
React Hook Form also provides performance improvements by having smarter and more optimized re-rendering.
The useForm Hook
React Hook Form's core functionality comes from its useForm
custom Hook. Recall that a custom Hook is basically just a function.
import { useForm } from "react-hook-form"
useForm
returns a bunch of props. (See the useForm documentation for the full list).
For now, we'll focus on the handleSubmit
and register
props:
import { useForm } from "react-hook-form"
export default function App() {
const { register, handleSubmit } = useForm()
return (
<form>
.
.
.
</form>
)
}
handleSubmit
handleSubmit
is a function that takes in a function. The passed-in function is what will happen when the form is submitted and deemed valid. For example, you could pass in a simple onSubmit
function:
import { useForm } from "react-hook-form"
export default function App() {
const { register, handleSubmit } = useForm()
const onSubmit = (data) => console.log(data)
return (
<form onSubmit={ handleSubmit(onSubmit) }>
.
.
.
<button type="submit">Submit</button>
</form>
)
}
handleSubmit
will:
- Validate the data when the form is submitted (more on this later).
- Call our
onSubmit
function, only after the validation passes.
Finally, we pass in handleSubmit
to be the form’s onSubmit
prop. This just says that when the form is submitted, call handleSubmit
. As stated before, handleSubmit
will then check if the data is valid, then call our onSubmit
function.
register
register
is a function that binds an input field to the form state:
import { useForm } from "react-hook-form"
export default function App() {
const { register, handleSubmit } = useForm()
//...
return (
<form onSubmit={ handleSubmit(onSubmit) >
<input type="text" {...register("name")} />
<select {...register("category")}>
<option value="">Select...</option>
<option value="A">Category A</option>
<option value="B">Category B</option>
</select>
<button type="submit">Submit</button>
</form>
)
}
Validation with Register
register
also lets you apply validation rules, which will be checked by handleSubmit
.
Some register
validation rules include:
required
: The field is required.min/maxLength
: The field must be at least/at most a certain number of characters.pattern
: The field must adhere to a certain regex patter.validate
: The field must return true when passed into a given function, which returns true if valid, and otherwise returns an error message.
See the full list of validation rules you can provide to register
in the documentation.
These rules are checked by handleSubmit
later when form is submitted.
Example
This example also shows how to pass in an error message. Displaying error messages will be discussed in the next section.
<input type="text" {...register("username", {
required: "Username is required",
minLength: {
value: 6,
message: "Username must have >=6 characters"
},
pattern: {
value: "/^[a-zA-Z]",
message: "Username must start with a letter"
},
validate: (value) => {
if (value.contains(" ")) {
return "Username cannot contain spaces";
}
return true;
}
})} />
Handling and Displaying Error Messages
Say the form is submitted, but the data doesn't pass validation. handleSubmit
will store any errors in the form state.
To display error messages, you will:
- Get
errors
fromformState
fromuseForm
. - Use conditional rendering to display an error message.
Step 1: Access Errors from Form State
In addition to register
and handleSubmit
, the useForm
custom Hook can also return a formState
prop. This is similar to how the built-in useState
Hook returns a state variable as one of its props. formState
is an object that covers the state of the entire form, including all fields in it, and contains properties that track information on the form. (See the documentation for a full list of formState
's properties).
One of formState
's properties is errors
. errors
is an object that contains the errors for each form field.
Access errors
from formState
from useForm
like so:
import { useForm } from "react-hook-form"
export default function App() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm()
...
}
Step 2: Conditionally Render Error Messages
Next, use conditional rendering to display an error message. Here, when the form is submitted with a blank username, “Username is required” is displayed under where you type in a username (after the form is submitted):
import { useForm } from "react-hook-form"
export default function App() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm()
...
return (
<form ...>
<input type="text" {...register("username", {
required: "Username is required"
})} />
{errors.username && (
<p>{errors.username.message</p>
)}
</form>
)
}
The below snippet conditionally renders the error message by checking if errors.registered_name
is present (i.e. not null
, undefined
, or any other falsy value). If so, then the expression evaluates to the second operand, which is the JSX element containing our error message. This element is then rendered.
{errors.registered_name && <>{errors.registered_name.message}</>}
TypeScript and Typing
TypeScript meshes well with React Hook Form by standardizing our form data and making sure inputted data conforms to expected typing. To add typing to our form with TypeScript, make the following simple changes.
Step 1: Create a custom Type
The custom Type should basically list out all input fields in your form and their type.
type Inputs = {
name: string
pet: string
}
Here, we named the custom Type Inputs
. It contains two properties, name
and pet
, both of which are string
. name
might
Step 2: Pass the custom Type into useForm
The useForm
Hook is a generic function. It can accept a type argument that defines the shape of the form's data.
Pass in the custom Type to useForm
:
type Inputs = {
name: string
pet: string
}
export default function App() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<Inputs>()
...
}
Step 3: Import the SubmitHandler Type
React Hook Form has a custom type called SubmitHandler
, which is used to define the type of the function that will handle form submissinos.
Import the SubmitHandler
type from React Hook Form and apply it to the function passed into handeSubmit. Based on our old example, we'll apply this type to the simple onSubmit
function we wrote:
import { useForm, SubmitHandler } from "react-hook-form"
type Inputs = {
name: string
pet: string
}
export default function App() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<Inputs>()
const onSubmit: SubmitHandler<Inputs> = (data) => {
console.log(data)
}
...
}
We also pass in our custom Inputs
type to SubmitHandler
. This ensures that the onSubmit
function receives form data that matches the Input
type we defined.
Overall Reference
Here is an overall reference:
import { useForm, SubmitHandler } from "react-hook-form"
type Inputs = {
name: string
pet: string
}
export default function App() {
const { register, handleSubmit, formState: { errors }} = useForm<Inputs>()
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data)
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="name">Name</label>
<input type="text" id="name" {...register("name", { required: "Name Required" })} />
{errors.name && <p>errors.name.message</p>}
<label htmlFor="pet">Choose a pet:</label>
<select id="pet" {...register("pet")} />
<option value="">---Choose an option---</option>
<option value="dog">Dog</option>
<option value="cat">Cat</option>
<option value="hamster">Hamster</option>
</select>
{errors.pet && <p>errors.pet.message</p>}
<button type="submit">Submit</button>
</form>
)
}