In order to ensure that a form element of your web application is returning valid data, it is helpful to build automated validation into your code. This is true in React as well, as creating form validation early on can often save you from encountering errors down the road.
In React, working with and validating forms can be a bit verbose. To make your code more manageable, you can use a package like Formik to build your forms.
In this tutorial, you will create a React project, add the Formik package, customize the Formik component with an onSubmit
callback and a validate
function for error messages, and then display those error messages to the user.
By the end of this tutorial, you will have a project like this live example on CodeSandbox.
Use Create React App to create a project. For the purposes of the tutorial, you can name your project validate-react-login-form.
- npx create-react-app validate-react-login-form
You can now change into the project directory, start the node server, and view it in a web browser.
- cd validate-react-login-form
- npm start
If you have yarn
installed, your message may instruct you to use yarn start
instead of npm start
. If you prefer to have npm
instructions, it is possible to use --use-npm
flag when creating your project. You can use either yarn
or npm
for this tutorial.
You can also open this project directory in your favorite editor to create and modify files.
Create React App will include several files, but for the purposes of this tutorial you will only be directly creating or modifying three files: index.js
, index.css
, and ValidatedLoginForm.js
.
Now that we have our initial project created, we will install three packages: Formik, email-validator, and Yup.
Formik makes handling validation, error messages, and form submission more manageable.
In your terminal, install Formik:
- npm install formik
email-validator is a tiny package used to validate emails.
If your terminal, install email-validator
:
- npm install email-validator
Yup is a schema validator that is commonly used in conjunction with Formik.
In your terminal, install Yup:
- npm install yup
Now that you’ve installed the necessary packages, you are ready to create the validated form component.
Now that you have installed the dependencies, you can start to write your ValidatedFormComponent
. For now, you will create the basics and import it into the root file in the app to display it.
To do this, you will do the following:
index.js
Create a new file in your src
directory called ValidatedLoginForm.js
. Inside of that file, add the basic code for a functional component:
import React from "react";
const ValidatedLoginForm = () => (
<div>
<h1>Validated Form Component</h1>
</div>
);
export default ValidatedLoginForm;
Then, include it in your index.js
file:
import ValidatedLoginForm from "./ValidatedLoginForm";
Next, reference the component:
<ValidatedLoginForm />
When you put all those pieces together, index.js
will look like this:
import React from "react";
import ReactDOM from "react-dom";
import ValidatedLoginForm from "./ValidatedLoginForm";
function App() {
return (
<div className="App">
<h1>Validated Login Form</h1>
<ValidatedLoginForm />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You will see the form component displayed:
Now, let’s revisit ValidatedLoginForm.js
to implement Formik.
First, import Formik, email-validator, and Yup in your new component:
import { Formik } from "formik";
import * as EmailValidator from "email-validator"; // used when validating with a self-implemented approach
import * as Yup from "yup"; // used when validating with a pre-built solution
Now, let’s write the Formik tag with initial values. Think of initial values as setting your state initially.
You’ll also need an onSubmit
callback. This callback will take two parameters, values and an object, that you can destructure. The values represent the input values from your form. You will add some dummy code here to simulate an async login call, then log out what the values are.
In the callback, call the setSubmitting
function that was destructured from the second parameters. This will allow you to enable or disable the Submit button while the asynchronous login call is happening:
<Formik
initialValues={{ email: "", password: "" }}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
console.log("Logging in", values);
setSubmitting(false);
}, 500);
}}
>
<h1>Validated Login Form</h1>
</Formik>
The Formik component uses render props to supply certain variables and functions to the form that we create.
In short, render props are used to pass properties to children elements of a component. In this case, Formik will pass properties to your form code, which is the child. Notice that you’re using destructuring to get a reference to several specific variables and functions.
{props => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleBlur,
handleSubmit
} = props;
return (
<div>
<h1>Validated Login Form</h1>
</div>
);
}}
At this point, ValidatedLoginForm.js
should resemble:
import React from "react";
import { Formik } from "formik";
import * as EmailValidator from "email-validator";
import * as Yup from "yup";
const ValidatedLoginForm = () => (
<Formik
initialValues={{ email: "", password: "" }}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
console.log("Logging in", values);
setSubmitting(false);
}, 500);
}}
>
{props => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleBlur,
handleSubmit
} = props;
return (
<div>
<h1>Validated Login Form</h1>
</div>
);
}}
</Formik>
);
export default ValidatedLoginForm;
You can now start to write the code to display the form. The form will have two inputs (email and password), labels for each, and a submit button.
{props => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleBlur,
handleSubmit
} = props;
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
type="text"
placeholder="Enter your email"
/>
<label htmlFor="password">Password</label>
<input
id="password"
name="password"
type="password"
placeholder="Enter your password"
/>
<button type="submit">
Login
</button>
</form>
);
}}
Notice that the onSubmit
is calling the handleSubmit
from the props.
Earlier, it was mentioned that you could disable your submit button while the user is already attempting to log in. You can add that change now by using the isSubmitting
property that you destructured from the previous props:
<button type="submit" disabled={isSubmitting}>
Login
</button>
You can use the following CSS for your styles.css
file:
.App {
font-family: sans-serif;
}
h1 {
text-align: center;
}
form {
max-width: 500px;
width: 100%;
margin: 0 auto;
}
label,
input {
display: block;
width: 100%;
}
label {
margin-bottom: 5px;
height: 22px;
}
input {
margin-bottom: 20px;
padding: 10px;
border-radius: 3px;
border: 1px solid #777;
}
input.error {
border-color: red;
}
.input-feedback {
color: rgb(235, 54, 54);
margin-top: -15px;
font-size: 14px;
margin-bottom: 20px;
}
button {
padding: 10px 15px;
background-color: rgb(70, 153, 179);
color: white;
border: 1px solid rgb(70, 153, 179);
transition: ease-in-out background-color 250ms, ease-in-out color 250ms;
}
button:hover {
cursor: pointer;
background-color: white;
color: rgb(70, 153, 179);
}
Also, import styles.css
in index.js
.
import "./styles.css";
Now let’s validate our inputs. The first step is to determine what constraints we want to have on our input. Let’s start with email. Email input should:
Password input should:
We’ll cover two ways to create these messages, one manually and one using Yup.
The first option is to create our validate function ourselves. The purpose of the function is to iterate through the values of our form, validate these values in whatever way we see fit, and return an errors
object that has key value pairs of value
and message
.
Inside of the Formik tag, start with adding the following code. This will always add an Invalid email
error for email.
validate={values => {
let errors = {};
errors.email = "Invalid email";
return errors;
}}
Now, you can ensure that the user has input something for the email:
validate={values => {
let errors = {};
if (!values.email) {
errors.email = "Required";
}
return errors;
}}
Then, you can check that the email is a valid-looking email by using the email-validator package. This will look almost the same as the equivalent check for email:
validate={values => {
let errors = {};
if (!values.email) {
errors.email = "Required";
} else if (!EmailValidator.validate(values.email)) {
errors.email = "Invalid email address.";
}
return errors;
}}
That takes care of email, so you will work on the password form. You will first check that the user input something:
validate={values => {
let errors = {};
if (!values.password) {
errors.password = "Required";
}
return errors;
}}
Now you need to ensure that the length is at least eight characters:
validate={values => {
const passwordRegex = /(?=.*[0-9])/;
if (!values.password) {
errors.password = "Required";
} else if (values.password.length < 8) {
errors.password = "Password must be 8 characters long.";
}
return errors;
}}
Lastly, check that the password contains at least one number. For this, you can use regex:
validate={values => {
let errors = {};
const passwordRegex = /(?=.*[0-9])/;
if (!values.password) {
errors.password = "Required";
} else if (values.password.length < 8) {
errors.password = "Password must be 8 characters long.";
} else if (!passwordRegex.test(values.password)) {
errors.password = "Invalid password. Must contain one number.";
}
return errors;
}}
The completed file will look like this:
validate={values => {
let errors = {};
if (!values.email) {
errors.email = "Required";
} else if (!EmailValidator.validate(values.email)) {
errors.email = "Invalid email address.";
}
const passwordRegex = /(?=.*[0-9])/;
if (!values.password) {
errors.password = "Required";
} else if (values.password.length < 8) {
errors.password = "Password must be 8 characters long.";
} else if (!passwordRegex.test(values.password)) {
errors.password = "Invalid password. Must contain one number.";
}
return errors;
}}
You might have noticed that handling the validate logic on our own gets a bit verbose. You have to manually do all of the checks. Yup can save you some of this work. When using Yup, you will no longer see the Validate
property, but instead use validationSchema
.
Let’s start with email. Here is the equivalent validation using Yup:
validationSchema={Yup.object().shape({
email: Yup.string()
.email()
.required("Required")
})}
Now, for password:
validationSchema={Yup.object().shape({
email: Yup.string()
.email()
.required("Required"),
password: Yup.string()
.required("No password provided.")
.min(8, "Password is too short - should be 8 chars minimum.")
.matches(/(?=.*[0-9])/, "Password must contain a number.")
})}
You’ve now explored two different methods for validating forms. Next you will update the code to display error messages.
Now that we have the logic for creating error messages, we need to display them. You will need to update the inputs in your form to do this.
We need to update several properties for both email and password inputs:
value
onChange
onBlur
className
Let’s start by updating value
, onChange
, and onBlur
. Each of these will use properties from the render props:
<input
id="email"
name="email"
type="text"
placeholder="Enter your email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>
Then you can add a conditional “error” class in case there are any errors. You can check for errors by looking at the errors
object.
You can also check the touched property to see whether or not the user has interacted with the email input before showing an error message.
<input
id="email"
name="email"
type="text"
placeholder="Enter your email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
className={errors.email && touched.email && "error"}
/>
Lastly, if there are errors, you will display them to the user.
The final result for the email field will look like this:
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
type="text"
placeholder="Enter your email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
className={errors.email && touched.email && "error"}
/>
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
Now you need to do the same with the password. These steps are similar to the email.
The final result for the password field will look like this:
<label htmlFor="password">Password</label>
<input
id="password"
name="password"
type="password"
placeholder="Enter your password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
className={errors.password && touched.password && "error"}
/>
{errors.password && touched.password && (
<div className="input-feedback">{errors.password}</div>
)}
Now that your form is complete, you are ready to test it out. You can start by clicking the button without entering anything. You will see validation messages:
Now, we can get more specific for testing messages. Refresh your page to do this. Click inside of the email input, but don’t type anything:
Then, click away from the input. You should see the Required
message pop up. Notice that this message doesn’t pop up automatically when the page loads. You only want to display error messages after the user has interacted with the input.
Now, start to type. You will get a message about the email not being valid.
Type in a valid email, and watch your error message go away.
Now, do the same for password. Click on the input, then away, and you’ll get the required message.
Then, start typing and you’ll see the length validation.
Then, type eight or more characters that do not include a number, and you’ll see the must contain a number
message.
Finally, add a number, and the error messages go away.
You’ve now created a form with automatic validation in React, using Formik and Yum.
For more tutorials on React, check out our React Topic page.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!