#tech
#frontend
#react
14 min read

Form Validation in React

Irakli Tchigladze

Handling user inputs is the key to building interactive web applications in React.

Forms are a very common and valuable tool for collecting user information. They can be used for authentication, registration, feedback, or to collect other important information.

In this article, we’ll talk about form validation - verifying user inputs to improve the quality of collected data. First, we’ll implement basic form validation from the ground up. Then we’ll explore Formik, a great library for building smart forms in React.

By the time you’re finished reading, you’ll be able to create forms and validate them easily.

Find guides similar to this one on SimpleFrontEnd.

Form Validation in React

React has a large community of developers who create libraries for important features like form validation. There are numerous great libraries for easy form validation in React, so typically, you won’t need to write all the code yourself.

We’re still going to explain how to implement basic form validation in React from scratch. Understanding low-level code can help you make better use of form validation libraries, which follow the same principles but add many great features on top of the basic functionality.

If you’re looking for a short answer, skip to the section where we discuss using Formik to validate forms in React.

State and controlled components in React

React components often have a state object, which stores essential information (data) to represent the most recent state of the component. React state often contains user inputs, so you can also say that it represents the most recent choices and inputs of the user.

React docs actually recommend storing user inputs in the state, which is a normal JavaScript object. So you can write a JavaScript function to verify user inputs and, if necessary, display helpful feedback to help users fix their mistakes.

In React, <input> components that get their value from the state are called controlled components.

Any time a user types or deletes something from the field, the onChange event handler will update the state, and the input field will show the updated value.

Implement form validation in React

User inputs are much easier to validate when stored in the state.

Create state variables

We’ll assume you’re already familiar with initializing and using state in React and not bore you with a detailed explanation.

For our practical example, let’s create three state variables:

const [inputFields, setInputFields] = useState({
   email: "",
   password: "",
   age: null
 });
 const [errors, setErrors] = useState({});
 const [submitting, setSubmitting] = useState(false);

inputFields object has three properties to store users’ input: email, password, and age. One for each field of the form we’re going to build.

When users enter something into one of the three fields, we’ll update the corresponding property of the inputFields object to reflect users’ most recent input.

errors object will store information about users’ errors.

The submitting variable will store a boolean that indicates whether the user is attempting to submit the form.

Function for validation

Next, let’s create a function to validate users’ inputs:

const validateValues = (inputValues) => {
   let errors = {};
   if (inputValues.email.length < 15) {
     errors.email = "Email is too short";
   }
   if (inputValues.password.length < 5) {
     errors.password = "Password is too short";
   }
   if (!inputValues.age || inputValues.age < 18) {
     errors.age = "Minimum age is 18";
   }
   return errors;
 };

Our validation function takes one argument - the object representing current input values.

In the function body, we create an object to store any possible errors.

First, we check the length of email value. If it’s under 15 characters long, we set the email property of the errors object to a custom error message.

Second, we check the length of the password value. If it’s under five characters long, we set the password property of the errors object to a custom error message.

Then we evaluate the age value. If it’s NULL (its initial value) or under 18, we set the age property of the errors object to a custom error message.

Finally, we return the errors object, which should contain the result of validation.

onChange handler

Next, we need an event handler to update the state every time a user enters (or deletes) values in the field.

const handleChange = (e) => {
   setInputFields({ ...inputFields, [e.target.name]: e.target.value });
 };

onSubmit handler

We also need to define what happens after the user submits the form.

Our handleSubmit function will use the preventDefault() method to stop the page from reloading.

The function also updates the errors state variable to the result of validation.

Finally, handleSubmit will run only if the user tries to submit the form, so we set submitting boolean to true.

const handleSubmit = (event) => {
   event.preventDefault();
   setErrors(validateValues(inputFields));
   setSubmitting(true);
 };

Supporting functions

Before we proceed, we want to make sure there are no errors in the submitted data. So we define the finishSubmit() function, executed only if two conditions are met: errors object has no properties or values, and submitting is set to true.

useEffect() runs every time there’s a change to the errors state variable. If the state of errors does change, we need to re-evaluate whether the form should be submitted.

You can also use the finishSubmit() function to clear the form after submit in React.

const finishSubmit = () => {
   console.log(inputFields);
 };

 useEffect(() => {
   if (Object.keys(errors).length === 0 && submitting) {
     finishSubmit();
   }
 }, [errors]);

Create the form

As a final step, let’s define React elements and the JSX structure of our form.

Let’s create a <form> element with three pairs of <label> and <input> elements.

<div className="App">
  <form>
    <div>
      <label for="email">Email</label>
      <input
        type="email"
        name="email"
        value={inputFields.email}
        onChange={handleChange}
      ></input>
      <label for="password">Password</label>
      <input
        type="password"
        name="password"
        value={inputFields.password}
        onChange={handleChange}
      ></input>
      <label for="password">Age</label>
      <input
        type="number"
        name="age"
        value={inputFields.age}
        onChange={handleChange}
      ></input>
    </div>{" "}
    <button type="submit">Submit Information</button>
  </form>
</div>

Each <input> element must have type, name, value, and onChange attributes.

You can work with a live demo in CodeSandbox.

Finish implementing form validation in React

First let’s set the form’s onSubmit attribute to the handler we previously defined.

<div className="App">
  <form onSubmit={handleSubmit}>
    <div>
      <label for="email">Email</label>
      <input
        type="email"
        name="email"
        value={inputFields.email}
        onChange={handleChange}
      ></input>
      <label for="password">Password</label>
      <input
        type="password"
        name="password"
        value={inputFields.password}
        onChange={handleChange}
      ></input>
      <label for="password">Age</label>
      <input
        type="number"
        name="age"
        value={inputFields.age}
        onChange={handleChange}
      ></input>
    </div>
    <button type="submit">Submit Information</button>
  </form>
</div>

Second, let’s display a message when the form is successfully submitted.

The text should probably display on top of the form, so we should add this code right before the <form> element.

<div className="App">
  {Object.keys(errors).length === 0 && submitting ? (
    <span className="success">Successfully submitted ✓</span>
  ) : null}
  <form onSubmit={handleSubmit}>
    <div>
      <label for="email">Email</label>
      <input
        type="email"
        name="email"
        value={inputFields.email}
        onChange={handleChange}
      ></input>
      <label for="password">Password</label>
      <input
        type="password"
        name="password"
        value={inputFields.password}
        onChange={handleChange}
      ></input>
      <label for="password">Age</label>
      <input
        type="number"
        name="age"
        value={inputFields.age}
        onChange={handleChange}
      ></input>
    </div>
    <button type="submit">Submit Information</button>
  </form>
</div>

Once again, the success message will only display if there are no errors and if submitting is true.

Third, let’s add error messages that only render if there are errors.

For email, the code would look something like this:

{errors.email ? (
  <p className="error">
    Email should be at least 15 characters long
  </p>
) : null}

A simple ternary operator that returns a paragraph of text if there’s an error, and null (nothing) if there isn’t.

The same principle applies to password field:

{errors.password ? (
  <p className="error">
    Password should be at least 5 characters long
  </p>
) : null}

And a custom message for age errors:

{errors.age ? <p className="error"> Minimum age is 18</p> : null}

Live demo

You can try out the form yourself and play around with the source code on CodeSandbox.

Form Validation Library

React developers can use Formik to validate forms and display helpful error messages if needed.

What is Yup?

Yup is a schema creator and a very useful tool for form validation. You can use it to validate input values by their type, minimum and maximum length, requiredness, and even specify custom error messages for each type of error.

Formik allows you to use Yup as well as normal JavaScript functions for validation. Yup is the most useful for setting up a complex set of rules.

Using Formik for Form Validation in React

Formik library keeps track of your form’s state and provides custom components for easy form validation in React.

<Formik> is the main custom component that gives us access to methods and values like current input values and errors. It can be used with standard <input> and <form> elements, as well as custom Form, Field, and ErrorMessage components from Formik library.

To get started, install Formik and import the main component.

import { Formik } from "formik"

Let’s use the <Formik> component with standard input elements first.

Our form has the same three fields as before - email, password and age.

Previously we initialized form values by passing an argument to the useState() hook.

With <Formik>, we initialize form values by setting the initialValues prop.

Let’s create an object that holds initial values.

const initialValues = {
  email: "",
  password: "",
  age: null
}

Object property names must match values of the name attribute on the input field.

<input type="text" name="email"></input>
<input type="text" name="password"></input>
<input type="number" name="age"></input>

Next, let’s write a simple function to validate input values, record errors in the object, and return it.

function validate(values) {
  let errors = {};
  if (inputValues.email.length < 15) {
    errors.email = "Email is too short";
  }
  if (inputValues.password.length < 5) {
    errors.password = "Password is too short";
  }
  if (!inputValues.age || inputValues.age < 18) {
    errors.age = "Minimum age is 18";
  }
  return errors;
}

Property names of the errors object must match the values of the name attribute on the input field. Formik will take care of the rest.

We’ll also show how to use Yup to set up a schema for validation.

Last, we define the submitForm function. It’s supposed to be called once the form is validated and values are to be submitted.

const submitForm = (values) => {
  console.log(values);
};

To keep things simple, we’ll simply log submitted values to the console.

JSX structure of the component

Now, let’s define component UI in JSX.

In the return statement, the form is wrapped with a custom <Formik> component, which has three attributes: initialValues, validate, and onSubmit. In the previous step, we defined all three - initialValues object as well as validate and submitForm functions.

Between the opening and closing tags of the <Formik> custom component, we define a render function that returns the JSX structure of the form.

const SignIn = () => {
  return (
    <Formik
          initialValues={initialValues}
          validate={validate}
          onSubmit={submitForm}

    >
      {(props) =>
        return (
          <form onSubmit={props.handleSubmit}>
            <label htmlFor="username">Enter username:</label>
            <input
                      type="text"
                      name="username"
                      value={props.values.username}
                      onChange={props.handleChange}
                      className={props.errors.username ? 
                      "highlight-error" : null}
            />
            {props.errors.username && (<p className="error-text">{props.errors.username}</p>)}
            <input
                      type="password"
                      name="password"
                      value={props.values.password}
                      onChange={props.handleChange}
                      className={props.errors.password ? 
                      "highlight-error" : null}
            />
            {props.errors.password && (<p className="error-text">{props.errors.password}</p>)}
            <button {props.isValid? “” : submit}>Submit</button>
          </form>
      )
    }
    </Formik>
  )
}

We use render props to access methods and values essential for form validation.

For example, we use the props.handleSubmit method to handle submit events on the <form> element.

Errors found during the validation process will be stored in the errors object. For example, we evaluate props.errors.username to conditionally customize the appearance of the input field or display an error message only if there’s an error.

props.handleChange method is set as the onChange event handler for each input field.

We can access the current values of input fields by reading props.values.

props.isValid is a boolean value to indicate whether the form is successfully validated. It’s true if there are no errors and false if there are. We can use it to conditionally disable or enable the submit button.

There are other render props, such as: touched, handleBlur, and dirty values, but we’re not going to use them. Check out the API reference to learn more about these props.

Create validation schema with Yup

Let’s explore Yup and how it saves us a lot of time on writing validation.

const schema = Yup.object().shape({
  username: Yup.string().required('please enter username')
  password:  Yup.string().required('please enter password').min(4, 'password must be at least four characters long')
})

We use a chain of methods to set up a custom condition. The string() method specifies the type of value. The required() method specifies that the value is required. You can set a custom error message by passing a string as an argument to the method.

Validation rules for password values are similar to those of username. There’s also a third min() method with two arguments - minimum number of characters and custom error message if the minimum character is not met.

Instead of a validate function, we’ll use this basic schema to validate the form. Read docs to learn about setting more complex rules.

If you’re going to use Yup schema for validation, use validationSchema prop on the <Formik> component.

<Formik
      initialValues={initialValues}
      validationSchema={schema}
      onSubmit={submitForm}
>
  {/* input fields and error messages */}
</Formik>

Using Formik with custom input components

Now let’s look at custom components from the Formik library.

Custom Form Field, ErrorMessage components for simpler form validation in React.

Form custom component is bound with the main Formik component.

For example, <form> React element requires an explicit onSubmit attribute, but <Form> does not.

const SignIn = () => {
  return (
    <Formik
          initialValues={initialValues}
          validationSchema={schema}
          onSubmit={submitForm}
    >
      {(props) =>
        return (
          <form onSubmit={props.handleSubmit}>
            {/* input fields */}
          </form>
        )
      }
    </Formik>
  )
}

The <Form> custom component is automatically linked with the main <Formik> component. There is no need to explicitly set the onSubmit value.

const SignIn = () => {
  return (
    <Formik
          initialValues={initialValues}
          validationSchema={schema}
          onSubmit={submitForm}
    >
      {(props) =>
        return (
          <Form>
            {/* input fields */}
          </Form>
        )
      }
    </Formik>
  )
}

Next, let’s discuss individual input fields. Standard <input> elements require the onChange attribute.

const SignIn = () => {
  return (
    <Formik
          initialValues={initialValues}
          validationSchema={schema}
          onSubmit={submitForm}
    >
      {(formik) =>
        return (
        <Form>
          <input
                  type="text"
                  name="username"
                  value={formik.values.username}
                  onChange={formik.handleChange}
                  className={formik.errors.username ? 
                  "highlight-error" : null}
          />
          {formik.errors.username && (<p className="error-text">{formik.errors.username}</p>)}
        </Form>
        )
      }
    </Formik>
  )
}

Field and ErrorMessage custom components do not require explicit value or onChange attributes.

Formik looks at the name attribute to automatically update values and errors objects without an explicit onChange handler, which results in cleaner syntax.

These components do not require an explicit value attribute for the same reason. Formik maintains the form state, so it looks at the name attribute to get relevant values from the state.

const SignIn = () => {
  return (
    <Formik
          initialValues={initialValues}
          validationSchema={schema}
          onSubmit={submitForm}
    >
      {(formik) =>
        return (
          <Form>
            <Field
                  type="text"
                  name="username"
            />
            {formik.errors.username && (<p className="error-text">{formik.errors.username}</p>)}
          </Form>
        )
      }
    </Formik>
  )
}

Finally, let’s look at the ErrorMessage custom component.

Previously, we had to manually check if formik.errors.username is true to display the error message.

ErrorMessage custom component is much easier to use. It only renders if the current value is invalid.

ErrorMessage also allows you to specify the underlying HTML element. For example, specify whether the error message should be a <p> or <span> element.

You can also set the custom className value to customize the appearance of the error message. Change the text color to red, for example.

const SignIn = () => {
  return (
    <Formik
          initialValues={initialValues}
          validationSchema={schema}
          onSubmit={submitForm}
    >
      {(formik) =>
        return (
          <Form>
            <Field
                type="text"
                name="username"
            />
            <ErrorMessage
                name="email"
                component="span"
                className="error"
            />
          </Form>
        )
      }
    </Formik>
  )
}

Conclusion

User inputs are unpredictable, so most web applications require form validation to manage them. In this article, we explained concepts like state and controlled components to help you better understand form validation in React.

We also walked you through two different ways to implement form validation in React. First, we showed how to implement basic form validation without external libraries. Then we explored Formik library and its use for comprehensive form validation in React.

Visit SimpleFrontEnd to read more guides similar to this one.