Tutorial

Validating a Login Form With React

Draft updated on Invalid Date
Default avatar

By James Quick

Validating a Login Form With React

This tutorial is out of date and no longer maintained.

Introduction

For almost every form that you create, you will want some sort of validation. In React, working with and validating forms can be a bit verbose, so in this article, we are going to use a package called Formik to help us out!

  • Create a React project
  • Add the Formik (and Yup) packages
  • Customize the Formik component with an onSubmit callback and a validate function for error messages
  • then display those error messages to the user.

Here’s a sneak peek at what we are going to create.

https://codesandbox.io/s/4203r4582w

Creating the React Project

For this demo, I’ll be using CodeSandbox. You can use CodeSandbox as well or use your local environment. Totally up to you.

Regardless of what you use for this demo, you need to start with a new React app using Create React App. In CodeSandbox, I’m going to choose to do just that.

Installing Necessary Packages

Now that we have our initial project created, we need to install three packages.

  • Formik - makes handling validation, error messages, and form submission easier
  • Email-validator - tiny package to validate emails (I hope this one is self-explanatory : )
  • Yup - schema validator that is commonly used in conjunction with Formik

Formik

In your terminal, you’ll need to install Formik.

  1. npm install Formik

I’ll do the same in the CodeSandbox dependency GUI.

Email-Validator

Now install email-validator.

  1. npm install email-validator

Again installing from the CodeSandbox GUI.

Yup

  1. npm install Yup

And again in CodeSandbox.

Creating the Validated Form Component

Now, we can start to stub out our ValidatedFormComponent. For now, we just want to create the basics and import them into the root file in the app to see it get displayed.

  • Create a new functional component
  • Add dummy display content
  • Import in index.js

So, 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.

function App() {
  return (
    <div className="App">
      <ValidatedLoginForm />
    </div>
  );
}

And you should see it displayed.

Now, let’s start with the Formik stuff. First, import Formik, Email-Valiator, and Yup in your new component.

import { Formik } from "formik";
import _ as EmailValidator from "email-validator";
import _ as Yup from "yup";

Now, let’s stub out 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 we can destructure. The values represented the input values from your form. I’m adding some placeholder code here to simulate an async login call, then logging out what the values are.

In the callback, I’m also calling the setSubmitting function that was destructured from the second parameter. This will allow us to enable/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>

Render Props

The Formik component uses render props to supply certain variables and functions to the form that we create. If you’re not very familiar with render props, I would take a second to check out Render Props Explained.

In short, render props are used to pass properties to children elements of a component. In this case, Formik will pass properties to our form code, which is the child. Notice that I’m 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>
      );
    }}

Display the Form

Now, we can actually start to write the code to display the form. For what it’s worth, in the finished CodeSandbox, I also created a LoginForm**.js component to show how basic login forms are handled from scratch. You can also use that as a reference for the form we are going to add now.

The form is pretty simple with 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 name="email" type="text" placeholder="Enter your email" />

          <label htmlFor="email">Password</label>
          <input
            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.

I mentioned earlier that we could disable our submit button while the user is already attempting to log in. We can add that small change now by using the isSubmitting property that we destructured from props above.

  <button type="submit" disabled={isSubmitting}>
      Login
  </button>

I would recommend adding the CSS from the finished CodeSandbox as well. Otherwise, you won’t get the full effect. You can copy the below css into 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);
  background-color: 250ms;
}

button:hover {
  cursor: pointer;
  background-color: white;
  color: rgb(70, 153, 179);
}

Adding Validation Messages Logic

Now we need to figure out how to validate our inputs. The first question is, what constraints do 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 8 characters long
  • contain at least one number

We’ll cover two ways to create these messages, one using Yup and one doing it yourself. We recommend using Yup and you’ll see why shortly.

Doing it Yourself

The first option is creating our validate function. 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, you can add the following code. This will always add an “Invalid email” error for email. We will start with this and go from there.

    validate={values => {
      let errors = {};
      errors.email = "Invalid email";
      return errors;
    }}

Now, we can ensure that the user has input something for the email.

validate={values => {
      let errors = {};
      if (!values.email) {
        errors.email = "Required";
      } 
 return errors;
}}

Then, we can check that the email is actually 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 now for password. We can first check that the user input something.

validate={values => {
      let errors = {};
      if (!values.password) {
        errors.password = "Required";
      } 
 return errors;
}}

Now we need to check the length to be at least 8 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;
    }}

And lastly, that the password contains at least one number. For this, we can use regular expressions.

 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 = "Invalida password. Must contain one number";
      }

      return errors;
    }}

Here’s the whole thing.

  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 = "Invalida password. Must contain one number";
      }

      return errors;
    }}

Using Yup

Ok, you might have noticed that handling the validate logic on our own gets a bit verbose. We have to manually do all of the checks ourselves. It wasn’t that bad I guess, but with the Yup package, it gets all the easier!

Yup is the recommended way to handle validation messages.

When using Yup, we no longer will 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")
    })}

Much shorter right?! 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.")
})}

Pretty sweet!

Displaying Validation/Error Messages

Now that we have the logic for creating error messages, we need to display them. We will need to update the inputs in our form a bit.

We need to update several properties for both email and password inputs.

  • value
  • onChange
  • onBlur
  • className

Email

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

<input
  name="email"
  type="text"
  placeholder="Enter your email"
  value={values.email}
  onChange={handleChange}
  onBlur={handleBlur}
/>

Then we can add a conditional “error” class if there are any errors. We can check for errors by looking at the errors object (remember how we calculated that object ourselves way back when).

We 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
  name="email"
  type="text"
  placeholder="Enter your email"
  value={values.email}
  onChange={handleChange}
  onBlur={handleBlur}
  className={errors.email && touched.email && "error"}
/>

And lastly, if there are errors, we will display them to the user. All in all, email will look like this.

<label htmlFor="email">Email</label>
<input
  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>
)}

Password

Now we need to do the same with password. I won’t walk through each step because they are exactly the same as email. Here’s the final code.

<label htmlFor="email">Password</label>
<input
  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>
)}

Test it Out

Let’s try it out! You can start by clicking the button without entering anything. You should 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. We only want to display error messages after the user has interacted with the input.

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

And lastly, type in a valid-looking email, and your error message goes away.

Now, repeat 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 8 or more characters that do not include a number, and you’ll see the “must contain a number” message.

And lastly, add a number, and error messages go away.

Conclusion

Whew, that was a long one! Again, validation can be a tricky thing, but with the help of a few packages, it becomes a bit easier. At the end of the day though, I think we’ve got a pretty legit login form!

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
James Quick

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


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!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel