React Form Validation With Formik And Yup

About The Author

Nefe is a Frontend Developer who enjoys learning new things and sharing his knowledge with others. More about Nefe ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

Forms are an integral part of how users interact with our websites and web applications. Validating the data the user passes through the form is a critical aspect of our jobs as web developers. However, it doesn’t have to be a pain-staking process. In this article, Nefe Emadamerho-Atori will show you how Formik handles the state of the form data, validates the data, and handles form submission.

As developers, it is our job to ensure that when users interact with the forms we set up, the data they send across is in the form we expect.

In this article, we will learn how to handle form validation and track the state of forms without the aid of a form library. Next, we will see how the Formik library works. We’ll learn how it can be used incrementally with HTML input fields and custom validation rules. Then we will set up form validation using Yup and Formik’s custom components and understand how Yup works well with Formik in handling Form validation. We will implement these form validation methods to validate a simple sign up form I have set up.

Note: This article requires a basic understanding of React.

Form Validation In React

On its own, React is powerful enough for us to be able to set up custom validation for our forms. Let’s see how to do that. We’ll start by creating our form component with initial state values. The following sandbox holds the code for our form:

Form validation without the use of a library

const Form = () => {
  const intialValues = { email: "", password: "" };
  const [formValues, setFormValues] = useState(intialValues);
  const [formErrors, setFormErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);
}

With the useState hook, we set state variables for the formValues, formErrors and isSubmitting.

  • The formValues variable holds the data the user puts into the input fields.
  • The formErrors variable holds the errors for each input field.
  • The isSubmitting variable is a boolean that tracks if the form is being submitted or not. This will be true only when there are no errors in the form.
const submitForm = () => {
    console.log(formValues);
  };

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

const handleSubmit = (e) => {
    e.preventDefault();
    setFormErrors(validate(formValues));
    setIsSubmitting(true);
  };

const validate = (values) => {
    let errors = {};
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
    if (!values.email) {
      errors.email = "Cannot be blank";
    } else if (!regex.test(values.email)) {
      errors.email = "Invalid email format";
    }
    if (!values.password) {
      errors.password = "Cannot be blank";
    } else if (values.password.length < 4) {
      errors.password = "Password must be more than 4 characters";
    }
    return errors;
  };

useEffect(() => {
    if (Object.keys(formErrors).length === 0 && isSubmitting) {
      submitForm();
    }
  }, [formErrors]);

Here, we have 4 form handlers and a useEffect set up to handle the functionality of our form.

  • handleChange
    This keeps the inputs in sync with the formValues state and updates the state as the user types.
  • validate
    We pass in the formValues object as a argument to this function, then based on the email and password meeting the validation tests, the errors object is populated and returned.
  • handleSubmit
    Whenever the form is submitted, the formErrors state variable is populated with whatever errors may exist using the setFormErrors(validate(formValues)) method.
  • useEffect
    Here, we check if the formErrors object is empty, and if isSubmitting is true. If this check holds true, then the submitForm() helper is called. It has single dependency, which is the formErrors object. This means it only runs when the formErrors object changes.
  • submitForm: this handles the submission of the form data.
return (
    <div className="container">
      <h1>Sign in to continue</h1>
      {Object.keys(formErrors).length === 0 && isSubmitting && (
        <span className="success-msg">Signed in successfully</span>
      )}
      <form onSubmit={handleSubmit} noValidate>
        <div className="form-row">
          <label htmlFor="email">Email</label>
          <input
            type="email"
            name="email"
            id="email"
            value={formValues.email}
            onChange={handleChange}
            className={formErrors.email && "input-error"}
          />
          {formErrors.email && (
            <span className="error">{formErrors.email}</span>
          )}
        </div>
        <div className="form-row">
          <label htmlFor="password">Password</label>
          <input
            type="password"
            name="password"
            id="password"
            value={formValues.password}
            onChange={handleChange}
            className={formErrors.password && "input-error"}
          />
          {formErrors.password && (
            <span className="error">{formErrors.password}</span>
          )}
        </div>
        <button type="submit">Sign In</button>
      </form>
    </div>
  );

Here, we pass in the handleChange helper functions to the inputs’ onChange attribute. We link the value of the inputs to the formValues object, making them controlled inputs. From the React docs, controlled inputs are inputs whose values are controlled by React. An input-error style is applied if there are any errors related to that specific input field. An error message is conditionally displayed beneath each input if there are any errors related to that specific input field. Finally, we check if there are any errors in the errors object and if isSubmitting is true. If these conditions hold true, then we display a message notifying the user that they signed in successfully.

With this, we have a fully functional and validated form set up without the aid of a library. However, a form library like Formik with the aid of Yup can simplify the complexities of handling forms for us.

What Are Formik And Yup?

Right from the docs:

“Formik is a small library that helps you with the 3 most annoying parts in handling forms:
  1. Getting values in and out of form state.
  2. Validation and error messages
  3. Handling form submission.

Formik is a flexible library. It allows you to decide when and how much you want to use it. We can control how much functionality of the Formik library we use. It can be used with HTML input fields and custom validation rules, or Yup and the custom components it provides. Formik makes form validation easy! When paired with Yup, they abstract all the complexities that surround handling forms in React.

Yup is a JavaScript object schema validator. While it has many powerful features, we’ll focus on how it helps us create custom validation rules so we don’t have to. This is a sample Yup object schema for a sign-up form. We’ll go into Yup and how it works in depth later in the article.

const SignUpSchema = Yup.object().shape({
  firstName: Yup.string()
    .min(2, "Too Short!")
    .max(50, "Too Long!")
    .required("Firstname is required"),

  lastName: Yup.string()
    .min(2, "Too Short!")
    .max(50, "Too Long!")
    .required("Lastname is required"),

  phoneNumber: Yup.string()
    .required("Phone number is required")
    .matches(
/^([0]{1}|\+?[234]{3})([7-9]{1})([0|1]{1})([\d]{1})([\d]{7})$/g,
      "Invalid phone number"
    ),

  email: Yup.string().email().required("Email is required"),

  password: Yup.string()
    .required("Password is required")
    .min(6, "Password is too short - should be 6 chars minimum"),
});

Formik, HTML Input Fields And Custom Validation Rules

The following sandbox holds the code for this form set up:

The first thing we have to do is install Formik.

npm i formik

Then we can go ahead to import it in the file where we’ll make use of it.

import { Formik } from "formik";

Before creating the component, we need to create an initialValues and validate object which we’ll pass as props to the Formik component when we set it up. initialValues and validate are code snippets, not normal words.

The decision to do this outside the component is not a technical one, but rather for readability of our code.

const initialValues = {
  email: "",
  password: ""
};

initialValues: is an object that describes the initial values of the respective form fields. The name given to each key in the initialValues must correspond with the value of the name of the input field we want Formik to watch.

const validate = (values) => {
  let errors = {};
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
  if (!values.email) {
    errors.email = "Email is required";
  } else if (!regex.test(values.email)) {
    errors.email = "Invalid Email";
  }
  if (!values.password) {
    errors.password = "Password is required";
  } else if (values.password.length < 4) {
    errors.password = "Password too short";
  }
  return errors;
};

validate: this accepts a function that handles the form validation. The function accepts an object in the form of data values as an argument and validates each property in the object based on the rules defined. Each key in the values object must correspond with the name of the input field.

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

onSubmit: This handles what happens after the user submits. The onSubmit prop takes a callback function that will only run when there are no errors, meaning the user inputs are valid.

const SignInForm = () => {
  return (
    <Formik
      initialValues={initialValues}
      validate={validate}
      onSubmit={submitForm}
    >
      {(formik) => {
        const {
          values,
          handleChange,
          handleSubmit,
          errors,
          touched,
          handleBlur,
          isValid,
          dirty
        } = formik;
        return (
            <div className="container">
              <h1>Sign in to continue</h1>
              <form onSubmit={handleSubmit}>
                <div className="form-row">
                  <label htmlFor="email">Email</label>
                  <input
                    type="email"
                    name="email"
                    id="email"
                    value={values.email}
                    onChange={handleChange}
                    onBlur={handleBlur}
                    className={errors.email && touched.email ? 
                    "input-error" : null}
                  />
                  {errors.email && touched.email && (
                    <span className="error">{errors.email}</span>
                  )}
                </div>

                <div className="form-row">
                  <label htmlFor="password">Password</label>
                  <input
                    type="password"
                    name="password"
                    id="password"
                    value={values.password}
                    onChange={handleChange}
                    onBlur={handleBlur}
                    className={errors.password && touched.password ? 
                     "input-error" : null}
                  />
                  {errors.password && touched.password && (
                    <span className="error">{errors.password}</span>
                  )}
                </div>

                <button
                  type="submit"
                  className={dirty && isValid ? "" : "disabled-btn"}
                  disabled={!(dirty && isValid)}>
                  Sign In
                </button>
              </form>
            </div>
        );
      }}
    </Formik>
  );
};

We pass in the initialValues object, and the submitForm and validate functions we defined earlier into Formik’s initialValues, onSubmit and validate props respectively.

Using the render props pattern, we have access to even more props the Formik API provides.

  1. values
    This holds the values of the user inputs.
  2. handleChange
    This is the input change event handler. It is passed to the input field <input onChange={handleChange}>. It handles the changes of the user inputs.
  3. handleSubmit
    The form submission handler. It is passed into the form <form onSubmit={props.handleSubmit}>. This fires the function passed into the onSubmit prop whenever the form is submitted.
  4. errors
    This object holds the validation errors that correspond to each input field, and is populated with the definitions we passed into the Yup object schema.
  5. touched
    This is an object that watches if a form field has been touched. Each key corresponds to the name of the input elements and has a boolean value.
  6. handleBlur
    This is the onBlur event handler, and it is passed to the input field <input onBlur={handleBlur} />. When the user removes focus from an input, this function is called. Without it, if there are any errors in the input when it loses focus, the errors will only display when the user tries to submit.
  7. isValid
    Returns true if there are no errors (i.e. the errors object is empty) and false otherwise.
  8. dirty
    This prop checks if our form has been touched or not. We can use this to disable our submit button when the form loads initially.

When the form is submitted, Formik checks if there are any errors in the errors object. If there are, it aborts the submission and displays the errors. To display the span using HTML inputs, we conditionally render and style the error message of each respective input field if the field has been touched and there are errors for that field.

<button
  type="submit"
  className={!(dirty && isValid) ? "disabled-btn" : ""}
  disabled={!(dirty && isValid)}>
      Sign In
</button>

Also, we can add a visual cue to the button. The button is conditionally styled and disable it if there are errors in the errors object using the isValid and the dirty props.

Validation Using Formik’s Components And Yup

This sandbox holds the final code for this setup.

npm i yup
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";

We install Yup, import the Field, Form, and the ErrorMessage components from Formik.

Formik makes form validation easy! When paired with Yup, they abstract all the complexities that surround handling forms in React. With that we can then go ahead to create the schema we’ll be using for the sign in form using Yup. Instead of creating custom validations for each possible input field, which can be tedious, depending on the number of fields there are, we can leave that to Yup to handle.

const SignInSchema = Yup.object().shape({
  email: Yup.string().email().required("Email is required"),

  password: Yup.string()
    .required("Password is required")
    .min(4, "Password is too short - should be 4 chars minimum"),
});

Yup works similarly to how we define propTypes in React. We created an object schema with Yup’s object function. We define the shape of the validation object schema and pass it into Yup’s shape() method. The required() method. This method takes a string as an argument, and this string will be the error message. that displays whenever a required field is left blank.

This schema has two properties:

  • An email property that is a string type and is required.
  • A password property that is of number type but is not required.

We can chain validation is Yup as seen above. The properties of the schema object match the name of the input fields. The docs go into the different validation methods available in Yup.

const SignInForm = () => {
  return (
    <Formik
      initialValues={initialValues}
      validationSchema={signInSchema}
      onSubmit={(values) => {
        console.log(values);
      }}
    >
      {(formik) => {
        const { errors, touched, isValid, dirty } = formik;
        return (
          <div className="container">
            <h1>Sign in to continue</h1>
            <Form>
              <div className="form-row">
                <label htmlFor="email">Email</label>
                <Field
                  type="email"
                  name="email"
                  id="email"
                  className={errors.email && touched.email ? 
                  "input-error" : null}
                />
                <ErrorMessage name="email" component="span" className="error" />
              </div>

              <div className="form-row">
                <label htmlFor="password">Password</label>
                <Field
                  type="password"
                  name="password"
                  id="password"
                  className={errors.password && touched.password ? 
                  "input-error" : null}
                />
                <ErrorMessage
                  name="password"
                  component="span"
                  className="error"
                />
              </div>

              <button
                type="submit"
                className={!(dirty && isValid) ? "disabled-btn" : ""}
                disabled={!(dirty && isValid)}
              >
                Sign In
              </button>
            </Form>
          </div>
        );
      }}
    </Formik>
  );
};

While using HTML input fields get the job done, Formik’s custom components make things even easier for us, and reduce the amount of code we have to write! What are these custom components Formik provides us?

  1. Formik
    We’ve been using this for a while now. This is required for the other components to be usable.
  2. Form
    A wrapper that wraps the HTML <form/> element. It automatically links the onSubmit method to the form’s submit event.
  3. Field
    In the background, this automatically links the form input’s onChange, onBlur and value attributes to Formik’s handleChange, handleBlur, and values object respectively. It uses the name prop to match up with the state and automatically keeps the state in sync with the input value. With this component, we can decide to display it as an input field we want using it’s as property. For example, will render a textarea. By default, it renders an HTML input field.
  4. ErrorMessage
    It handles rendering the error message for its respective field based on the value given to the name prop, which corresponds to the <Field />’s name prop. It displays the error message if the field has been visited and the error exists. By default, it renders a string is the component prop is not specified.

We pass the signInSchema into Formik using the validationSchema prop. The Formik team loves the Yup validation library so they created a specific prop for Yup called validationSchema which transforms errors into objects and matches against their values and touched functions.

Conclusion

Users do not know or care how you handle form validation. However, for you the developer, it should be as painless a process as possible, and I believe Formik stands out as a solid choice in that regard.

We have successfully looked at some of the options available to us when validating forms in React. We have seen how Formik can be used incrementally, and how it pairs well with Yup in handling form validation.

Resources

Further Reading

Smashing Editorial (ks, ra, yk, il, mrn)