Tutorial

How To Validate a Login Form With React and Formik

DevelopmentReact

Introduction

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.

Prerequisites

Step 1 — Setting up the Project

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.

Step 2 — Installing Dependencies

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.

Step 3 — Creating 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:

  • Create a new functional component
  • Add dummy display content
  • Import it into 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:

src/ValidatedLoginForm.js
import React from "react";

const ValidatedLoginForm = () => (
  <div>
    <h1>Validated Form Component</h1>
  </div>
);

export default ValidatedLoginForm;

Then, include it in your index.js file:

src/index.js
import ValidatedLoginForm from "./ValidatedLoginForm";

Next, reference the component:

src/index.js
<ValidatedLoginForm />

When you put all those pieces together, index.js will look like this:

src/index.js
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:

Validated Form Component in CodeSandbox

Now, let’s revisit ValidatedLoginForm.js to implement Formik.

First, import Formik, email-validator, and Yup in your new component:

src/ValidatedLoginForm.js
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:

src/ValidatedLoginForm.js
  <Formik
    initialValues={{ email: "", password: "" }}
    onSubmit={(values, { setSubmitting }) => {
      setTimeout(() => {
        console.log("Logging in", values);
        setSubmitting(false);
      }, 500);
    }}
  >
    <h1>Validated Login Form</h1>
  </Formik>

Render Props

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.

src/ValidatedLoginForm.js
    {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;

Step 4 — Displaying the Form

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.

src/ValidatedLoginForm.js
    {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:

src/ValidatedLoginForm.js
<button type="submit" disabled={isSubmitting}>
  Login
</button>

You can use the following CSS for your styles.css file:

src/styles.css
.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.

src/index.js
import "./styles.css";

Step 5 — Adding Validation Messages Logic

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:

  • Be required.
  • Look like a real email.

Password input should:

  • Be required.
  • Be at least eight characters long.
  • Contain at least one number.

We’ll cover two ways to create these messages, one manually and one using Yup.

Writing Your Own Password Validation Solution

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.

src/ValidatedLoginForm.js
  validate={values => {
    let errors = {};
    errors.email = "Invalid email";
    return errors;
  }}

Now, you can ensure that the user has input something for the email:

src/ValidatedLoginForm.js
  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:

src/ValidatedLoginForm.js
  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:

src/ValidatedLoginForm.js
  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:

src/ValidatedLoginForm.js
  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:

src/ValidatedLoginForm.js
  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:

src/ValidatedLoginForm.js
  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;
  }}

Utilizing a Third-party Password Validation Solution

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:

src/ValidatedLoginForm.js
  validationSchema={Yup.object().shape({
    email: Yup.string()
      .email()
      .required("Required")
  })}

Now, for password:

src/ValidatedLoginForm.js
  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.

Step 6 — Displaying Validation and 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

Applying Render Props to the Email Field

Let’s start by updating value, onChange, and onBlur. Each of these will use properties from the render props:

src/ValidatedLoginForm.js
<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.

src/ValidatedLoginForm.js
<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:

src/ValidatedLoginForm.js
<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>
)}

Applying Render Props to the Password Field

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:

src/ValidatedLoginForm.js
<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>
)}

Step 7 — Testing the Validation

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:

Validation messages in the login form

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:

Focusing the email field in the login form

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.

Email validation error in the login form

Now, start to type. You will get a message about the email not being valid.

Invalid email message in the login form

Type in a valid email, and watch your error message go away.

Valid email in the login form

Now, do the same for password. Click on the input, then away, and you’ll get the required message.

The required password message in the login form

Then, start typing and you’ll see the length validation.

Length validation message in the login form

Then, type eight or more characters that do not include a number, and you’ll see the must contain a number message.

"must contain a number" message in the login form

Finally, add a number, and the error messages go away.

Valid password in the login form

Conclusion

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.

Creative Commons License