Hello everyone, today I'll be guiding us on how to add form validation to our React application using Yup and react hook form.
Video version
Video version youtube
Getting started, I've generated a new react project and setup tailwind css.
Incase you want to add tailwind css to your react project please follow this guide tailwindcss-react
This is what I currently have:
App.js
function App() {
return <div className="w-screen h-screen bg-gradient-to-r from-blue-900 to-purple-900 grid place-content-center">
</div>;
}
export default App;
Next let's create a components folder that will hold our Form component
src/components/Form/Form.jsx
At this point we're going to create our form component and have our various form inputs
const textInputClassName =
"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500";
const Form = () => {
return (
<div className="md:w-[500px] shadow-sm shadow-white bg-white w-[320px] mx-auto px-7 py-4 rounded-xl">
<form className="w-full">
<div className="mb-6">
<label
htmlFor="email"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Your email
</label>
<input
type="email"
id="email"
className={textInputClassName}
placeholder="test@test.com"
/>
</div>
<div className="mb-6">
<label
htmlFor="password"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Your password
</label>
<input type="password" id="password" className={textInputClassName} />
</div>
<div className="mb-6">
<label
htmlFor="confirmPassword"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Confirm Password
</label>
<input
type="password"
id="confirmPassword"
className={textInputClassName}
/>
</div>
<div className="mb-6">
<label
htmlFor="accountType"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400"
>
Select an option
</label>
<select
id="accountType"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
<option value="">Account Type</option>
<option value="personal">Personal</option>
<option value="commercial">Commercial</option>
</select>
</div>
<div className="flex justify-between mb-6">
<div className="flex">
<div className="flex items-center h-5">
<input
id="remember"
type="checkbox"
value=""
className="w-4 h-4 bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"
/>
</div>
<label
htmlFor="remember"
className="ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Remember me
</label>
</div>
<div>
<label
htmlFor="default-toggle"
className="inline-flex relative items-center cursor-pointer"
>
<input
type="checkbox"
value=""
id="default-toggle"
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">
Toggle me
</span>
</label>
</div>
</div>
<button
type="submit"
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Submit
</button>
</form>
</div>
);
};
export default Form;
Now we're done with our Form component JSX, let's go on and add our form to App.js
import Form from "./components/Form/Form";
function App() {
return (
<div className="w-screen h-screen bg-gradient-to-r from-blue-900 to-purple-900 grid place-content-center">
<Form />
</div>
);
}
export default App;
Our App.js now looks like this which gives us this result
Now we've our form design, let's proceed to adding validation. We need to install the following packages
npm install -D yup @hookform/resolvers react-hook-form
or incase you use yarn
yarn add -D yup @hookform/resolvers react-hook-form
Yup is going to be our schema builder for value parsing and validation,
React-hook-form is going to help us validate our form input,
@hookform/resolvers is used to integrate yup and react-hook-form nicely.
Let's import the packages we just installed in our Form component
/components/Form/Form.jsx
import { useForm } from "react-hook-form";
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
Before we start building our ValidationSchema, we need to add a name attribute to our html form, as this is important for yup and react-hook-form to keep track of our different inputs.
<form className="w-full">
<div className="mb-6">
<label
htmlFor="email"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Your email
</label>
<input
type="email"
name="email"
id="email"
className={textInputClassName}
placeholder="test@test.com"
/>
</div>
<div className="mb-6">
<label
htmlFor="password"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Your password
</label>
<input type="password" id="password" className={textInputClassName} />
</div>
<div className="mb-6">
<label
htmlFor="confirmPassword"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Confirm Password
</label>
<input
name="password"
type="password"
id="confirmPassword"
className={textInputClassName}
/>
</div>
<div className="mb-6">
<label
htmlFor="accountType"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400"
>
Select an option
</label>
<select
name="accountType"
id="accountType"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
<option value="">Account Type</option>
<option value="personal">Personal</option>
<option value="commercial">Commercial</option>
</select>
</div>
<div className="flex justify-between mb-6">
<div className="flex">
<div className="flex items-center h-5">
<input
id="remember"
name="remember"
type="checkbox"
value=""
className="w-4 h-4 bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"
/>
</div>
<label
htmlFor="remember"
className="ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Remember me
</label>
</div>
<div>
<label
htmlFor="toggle"
className="inline-flex relative items-center cursor-pointer"
>
<input
type="checkbox"
name="toggle"
value=""
id="toggle"
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">
Accept
</span>
</label>
</div>
</div>
<button
type="submit"
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Submit
</button>
</form>
Let's build our validation schema, for this I'll create a new schema folder and inside a formSchema.js file.
let's write our formSchema, like this
import * as yup from "yup";
export const registerSchema = yup.object().shape({
email: yup
.string("email should be a string")
.email("please provide a valid email address")
.required("email address is required"),
});
The email key should match the name attribute in your jsx.
In our Form.js
import { registerSchema } from "../../schema/formSchema";
// Saving space
const Form = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: yupResolver(registerSchema),
});
// Saving space
}
register will be used to register our inputs with react-hook-form,
handleSubmit should be added to our form onSubmit and when we submit our form, will help use validate our form,
formState helps us keep track of our form state, in this case error state.
let's add this to our email input, please take note of {...register("email")}
and the error jsx
<label
htmlFor="email"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Your email
</label>
<input
{...register("email")}
type="email"
name="email"
id="email"
className={textInputClassName}
placeholder="test@test.com"
/>
{errors.email ? (
<span className="text-red-900">{errors.email.message}</span>
) : (
<></>
)}
on our form submit handler, let's add this
<form onSubmit={handleSubmit(formSubmitHandler)} className="w-full">
// saving space
</from
You'll notice we've passed formSubmitHandler which is our custom function that will be passed the form data automatically if validation passes
const formSubmitHandler = (data) => {
console.log(data);
};
With this our form validation is working already and we should have a result like this
validating password and confirm password
Let's add the following to our schema file
export const registerSchema = yup.object().shape({
email: yup
.string("email should be a string")
.email("please provide a valid email address")
.required("email address is required"),
password: yup
.string("password should be a string")
.min(5, "password should have a minimum length of 5")
.max(12, "password should have a maximum length of 12")
.required("password is required"),
confirmPassword: yup
.string("password should be a string")
.oneOf([yup.ref("password")])
.required("confirm password is required"),
});
Back to our Form.js, let's update our password and confirm password to this
<div className="mb-6">
<label
htmlFor="password"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Your password
</label>
<input
{...register("password")}
type="password"
name="password"
id="password"
className={textInputClassName}
/>
{errors.password ? (
<span className="text-red-900">{errors.password.message}</span>
) : (
<></>
)}
</div>
<div className="mb-6">
<label
htmlFor="confirmPassword"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Confirm Password
</label>
<input
{...register("confirmPassword")}
name="confirmPassword"
type="password"
id="confirmPassword"
className={textInputClassName}
/>
{errors.confirmPassword ? (
<span className="text-red-900">{errors.confirmPassword.message}</span>
) : (
<></>
)}
</div>
This gives us this result
Validating Select
Let's update our schema file to the following
import * as yup from "yup";
export const registerSchema = yup.object().shape({
email: yup
.string("email should be a string")
.email("please provide a valid email address")
.required("email address is required"),
password: yup
.string("password should be a string")
.min(5, "password should have a minimum length of 5")
.max(12, "password should have a maximum length of 12")
.required("password is required"),
confirmPassword: yup
.string("password should be a string")
.oneOf([yup.ref("password")])
.required("confirm password is required"),
accountType: yup
.string("account type should be a string")
.oneOf(["personal", "commercial"])
.required("account type is required"),
});
Now let's also update our select jsx
<div className="mb-6">
<label
htmlFor="accountType"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400"
>
Select an option
</label>
<select
{...register("accountType")}
name="accountType"
id="accountType"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
<option value="">Account Type</option>
<option value="personal">Personal</option>
<option value="commercial">Commercial</option>
</select>{" "}
{errors.accountType ? (
<span className="text-red-900">{errors.accountType.message}</span>
) : (
<></>
)}
</div>
Now we've this
Lastly, let's validate our toggle and checkbox
We start by updating our schema file
import * as yup from "yup";
export const registerSchema = yup.object().shape({
email: yup
.string("email should be a string")
.email("please provide a valid email address")
.required("email address is required"),
password: yup
.string("password should be a string")
.min(5, "password should have a minimum length of 5")
.max(12, "password should have a maximum length of 12")
.required("password is required"),
confirmPassword: yup
.string("password should be a string")
.oneOf([yup.ref("password")])
.required("confirm password is required"),
accountType: yup
.string("account type should be a string")
.oneOf(["personal", "commercial"])
.required("account type is required"),
remember: yup.boolean().oneOf([true], "Please tick checkbox"),
toggle: yup.boolean().oneOf([true], "Please toggle accept"),
});
Then let's update our from checkbox and toggle jsx
<div className="flex justify-between mb-6">
<div>
<div className="flex">
<div className="flex items-center h-5">
<input
{...register("remember")}
id="remember"
name="remember"
type="checkbox"
value=""
className="w-4 h-4 bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"
/>
</div>
<label
htmlFor="remember"
className="ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Remember me
</label>
</div>
{errors.remember ? (
<span className="text-red-900">{errors.remember.message}</span>
) : (
<></>
)}
</div>
<div>
<div>
<label
htmlFor="toggle"
className="inline-flex relative items-center cursor-pointer"
>
<input
{...register("toggle")}
type="checkbox"
name="toggle"
value=""
id="toggle"
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">
Accept
</span>
</label>
</div>
{errors.toggle ? (
<span className="text-red-900">{errors.toggle.message}</span>
) : (
<></>
)}
</div>
</div>
Whooaa with this, we're done with this result
Thank you for following, incase you need the final code, here's the github repo
Top comments (2)
Hi Alaribe,
Interesting article! It's clear and goes deep into the implementation details
but... honestly, is all of that code really needed?
It feels a bit like we are reinventing the wheel whenever it comes to form validation/submission. There're tens of libraries doing pretty much the same thing and every time we work on a different project we have to learn how to use the library adopted by that project.
HTML5 already gives us built-in authentication function. Do we really need to use a 3rd party lib?
I am exploring a vanilla React approach here:
dev.to/cuginoale/form-validationsu...
and I would love to get your perspective on this!
cheers
Great Alessio, I'll check it out and comment over there.
Thank you