This site runs best with JavaScript enabled.

Adding validations to React Hook Form

Daniel Afonso

June 25, 2021


Hey everyone, recently I've using React Hook Form a lot to manage my forms state. While it's quite useful, one question that I've heard quite a while is "how can I validate stuff in there?".

In this blog post, I'll show you how add validations to your forms created using React Hook Form by using Yup.

Validation our form

Here we have a very simple form with an uncontrolled input component being wrapped by a Controller and assigned to the username value by the name prop. This form should show danieljcafonso as a pre filled value for the input thanks to the defaultValues being passed to the useForm hook.

import { Controller, useForm } from 'react-hook-form'
const defaultValues = {
username: 'danieljcafonso',
}
export default function App() {
const { control } = useForm({
defaultValues,
})
return (
<form>
<Controller
render={({ field }) => <input {...field} />}
name="username"
control={control}
defaultValue={defaultValues.username}
/>
</form>
)
}

First, we need to install @hookforms/resolvers to be able to integrate with an external validation library like yup, zod, joi, superstruct, vest, and others.

npm install @hookforms/resolvers

Then, we install our validation library, in this case, I'll be using Yup.

npm install yup

Now that we have Yup installed, we can create our schema using it. In this scenario we'll create an object that inside of it will have username as a required string.

import * as yup from "yup";
const schema = yup.object().shape({
username: yup.string().required()
});
...

After we have our schema, we need to pass it to the React Hook Form useForm hook. For this, we pass it wrapped with the yupResolver function as a resolver. Also, we can define our validations mode.

const { control } = useForm({
defaultValues,
mode: 'onChange',
reValidateMode: 'onChange',
resolver: yupResolver(schema),
})

Here is the final outcome of these changes.

import { Controller, useForm } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import * as yup from 'yup'
const schema = yup.object().shape({
username: yup.string().required(),
})
const defaultValues = {
username: 'danieljcafonso',
}
export default function App() {
const { control } = useForm({
defaultValues,
mode: 'onChange',
reValidateMode: 'onChange',
resolver: yupResolver(schema),
})
return (
<form>
<Controller
render={({ field }) => <input {...field} />}
name="username"
control={control}
defaultValue={defaultValues.username}
/>
</form>
)
}

Validating Field Arrays

Field Arrays are very useful when dealing with fields that might have multiple values. While this is very useful, I've struggled in the past on how to validate this.

First, let's start by adding a users array to our defaultValues

const defaultValues = {
username: 'danieljcafonso',
users: [
{ id: 0, firstName: 'Daniel', lastName: 'Afonso' },
{ id: 1, firstName: 'Bruce', lastName: 'Wayne' },
],
}

Then, in our component let's use useFieldArray to extract the values of the users array of the current values of the form.

const { fields } = useFieldArray({
control,
name: 'users',
})

Then, let's use those fields to add to the DOM a new input to represent the values. Make sure that the name includes the position of the user in question to bind the values properly.

{
fields.map((user, index) => {
return (
<div key={user.id}>
<Controller
render={({ field }) => <input {...field} />}
name={`users.${index}.firstName`}
control={control}
defaultValue={user.firstName}
/>
<Controller
render={({ field }) => <input {...field} />}
name={`users.${index}.lastName`}
control={control}
defaultValue={user.lastName}
/>
</div>
)
})
}

Now, change our schema to add the users field and represent it as an array of objects where each object has a firstName and lastName required string.

const schema = yup.object().shape({
username: yup.string().required(),
users: yup.array().of(
yup.object().shape({
firstName: yup.string().required(),
lastName: yup.string().required(),
}),
),
})

After that everything should be working properly, here is the full version of our code.

import { Controller, useFieldArray, useForm } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import * as yup from 'yup'
const schema = yup.object().shape({
username: yup.string().required(),
users: yup.array().of(
yup.object().shape({
firstName: yup.string().required(),
lastName: yup.string().required(),
}),
),
})
const defaultValues = {
username: 'danieljcafonso',
users: [
{ id: 0, firstName: 'Daniel', lastName: 'Afonso' },
{ id: 1, firstName: 'Bruce', lastName: 'Wayne' },
],
}
export default function App() {
const { control } = useForm({
defaultValues,
mode: 'onChange',
reValidateMode: 'onChange',
resolver: yupResolver(schema),
})
const { fields } = useFieldArray({
control,
name: 'users',
})
return (
<form>
<Controller
render={({ field }) => <input {...field} />}
name="username"
control={control}
defaultValue={defaultValues.username}
/>
{fields.map((user, index) => {
return (
<div key={user.id}>
<Controller
render={({ field }) => <input {...field} />}
name={`users.${index}.firstName`}
control={control}
defaultValue={user.firstName}
/>
<Controller
render={({ field }) => <input {...field} />}
name={`users.${index}.lastName`}
control={control}
defaultValue={user.lastName}
/>
</div>
)
})}
</form>
)
}

Wrapping up

As you can see adding validations to React Hook Form is really straight forward, in my opinion, most of the challenges come from dealing with the validation library you choose to use. Here is a link to a codesandbox where you can try this code out.

I hope you found this blog post useful.

Stay tuned for more and see you next time!

Share article

Daniel Afonso © 2021