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.
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.
You will have a project like this live example on CodeSandbox:
Example built with React 16.8.6 and Formik 1.5.2
This is a quick example of how to setup form validation in React with the Formik library. Formik contains a higher order component that helps with managing react state, validation, error messages and form submission. Validation is done with the Yup object schema validator which hooks into Formik via the handy validationSchema
prop.
The example is a simple registration form with pretty standard fields for first name, last name, email, password and confirm password. All fields are required, the email field must be a valid email address, the password field must have a min length of 6 and must match the confirm password field.
Styling of the React + Formik example is all done with Bootstrap 4 CSS.
Here it is in action:
The app component contains an example registration form built with the <Formik />
component. The initial values of each field are set in the initialValues
property. Validation rules and error messages are set in the validationSchema
property. The onSubmit
property contains a function that gets called when the form is submitted and valid. The html and jsx markup for the form is set in the render
property.
The <Field />
and <ErrorMessage />
components are part of the Formik library that automatically hook up inputs and error messages with the fields defined in Formik. For more info about the helper components available check out the Formik docs.
import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
class App extends React.Component {
render() {
return (
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: ''
}}
validationSchema={Yup.object().shape({
firstName: Yup.string()
.required('First Name is required'),
lastName: Yup.string()
.required('Last Name is required'),
email: Yup.string()
.email('Email is invalid')
.required('Email is required'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Password is required'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password'), null], 'Passwords must match')
.required('Confirm Password is required')
})}
onSubmit={fields => {
alert('SUCCESS!! :-)\n\n' + JSON.stringify(fields, null, 4))
}}
render={({ errors, status, touched }) => (
<Form>
<div className="form-group">
<label htmlFor="firstName">First Name</label>
<Field name="firstName" type="text" className={'form-control' + (errors.firstName && touched.firstName ? ' is-invalid' : '')} />
<ErrorMessage name="firstName" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<label htmlFor="lastName">Last Name</label>
<Field name="lastName" type="text" className={'form-control' + (errors.lastName && touched.lastName ? ' is-invalid' : '')} />
<ErrorMessage name="lastName" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<label htmlFor="email">Email</label>
<Field name="email" type="text" className={'form-control' + (errors.email && touched.email ? ' is-invalid' : '')} />
<ErrorMessage name="email" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Field name="password" type="password" className={'form-control' + (errors.password && touched.password ? ' is-invalid' : '')} />
<ErrorMessage name="password" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<label htmlFor="confirmPassword">Confirm Password</label>
<Field name="confirmPassword" type="password" className={'form-control' + (errors.confirmPassword && touched.confirmPassword ? ' is-invalid' : '')} />
<ErrorMessage name="confirmPassword" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary mr-2">Register</button>
<button type="reset" className="btn btn-secondary">Reset</button>
</div>
</Form>
)}
/>
)
}
}
export { App };
The base index html file contains the outer html for the whole tutorial application.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React + Formik - Form Validation Example</title>
<!-- bootstrap css -->
<link href="//netdna.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" />
<style>
a { cursor: pointer; }
</style>
</head>
<body>
<div class="jumbotron">
<div class="container">
<div class="row">
<div class="col-md-6 offset-md-3">
<h3>React + Formik - Form Validation</h3>
<div id="app"></div>
</div>
</div>
</div>
</div>
</body>
</html>
The root index.jsx file bootstraps the react tutorial application by rendering the App
component into the #app
div element defined in the base index html file above.
import React from 'react';
import { render } from 'react-dom';
import { App } from './App';
render(
<App />,
document.getElementById('app')
);
Forms in React with Formik
#reactjs #javascript #web-development