React Form Validation With Formik and Yup

Last time out, I showed how we can build a React form using the form library called Formik. In this article, I will be showing you how to validate the form we created using Yup.

Below is how the form and code look like at the end of the first issue of this article:

const UserForm = () => {
    return (
      <Formik
        initialValues={{
          firstname: ''
          lastname: ''
          email: ''
          country: ''
          state: ''
          zip: '' 
        }}
        onSubmit={() => {
         console.log('form submitted')
        }}
      >
        { ({
          values,
          errors,
          touched,
          handleChange,
          handleBlur,
          handleSubmit
        }) => (
        <div className="container">

          <div className="col-md-12 mt-5">
            <form onSubmit={handleSubmit}>
              <h4 className="mb-3">Personal information</h4>

              <div className="row">
                <div className="col-md-6 mb-3">
                  <label htmlFor="firstname">First name</label>
                  <input 
                    type="text" 
                    className="form-control" 
                    id="firstname" 
                    name="firstname" 
                    value={values.firstname}
                  />
                </div>
                <div className="col-md-6 mb-3">
                  <label htmlFor="lastname">Last name</label>
                  <input 
                    type="text" 
                    className="form-control" 
                    id="lastname" 
                    name="lastname" 
                    value={values.lastname}
                  />
                </div>
              </div>

              <div className="mb-3">
                <label htmlFor="email">Email</label>
                <input 
                  type="email" 
                  className="form-control" 
                  id="email" 
                  name="email" 
                  placeholder="[email protected]" 
                  value={values.email}
                />
              </div>

              <div className="row">
                <div className="col-md-5 mb-3">
                  <label htmlFor="country">Country</label>
                  <select 
                    className="custom-select d-block w-100" 
                    id="country" 
                    name="country" 
                    value={values.country}
                  >
                    <option value="">Choose...</option>
                    <option value="NIG">Nigeria</option>
                    <option value="GH">Ghana</option>
                    <option value="SA">South Africa</option>
                  </select>        
                </div>
                <div className="col-md-4 mb-3">
                  <label htmlFor="state">State</label>
                  <select 
                    className="custom-select d-block w-100" 
                    id="state" 
                    name="state" 
                    value={values.state}
                  >
                    <option value="">Choose...</option>
                    <option value="lagos">Lagos</option>
                    <option value="east legion">East Legion</option>
                    <option value="cape town">Cape Town</option>
                  </select>             
                </div>
                <div className="col-md-3 mb-3">
                  <label htmlFor="zip">Zip</label>
                  <input 
                    type="text" 
                    className="form-control" 
                    id="zip" 
                    name="zip" 
                    value={values.zip}
                  />
                </div>
              </div>

              <hr className="mb-4"/>
              <button className="btn btn-primary btn-lg btn-block" type="submit">
            Submit
              </button>
           </form>
         </div>

       </div>
        ) }
      </Formik>
    )
}

If you are just coming across this article and you haven't read the first issue where I created the form using Formik, then you should probably go check out Building React Forms Painlessly With Formik

Now there are different ways we can validate our forms, we can validate the form manually with Formik or we can validate it using Yup. In this article, I will only be showing you the Yup way, because I have resolved to make this particular article less lengthy than the last one.

To begin we will have to install Yup by running npm install yup in our command line. Make sure the current directory you are in when running this command is your React project folder.

In the previous article, I named the file that houses the form as userForm.js. It is in this file we will be importing Yup as follows:

import * as Yup from 'yup'

Importing Yup into our file gives us access to a property called validationSchema that we can add to the Formik component

validationSchema = {Yup.object({
  firstname: Yup
    .string()
    .required('Sorry, this is required')
    .max(5, 'Sorry, name is too long'),
  lastname: Yup
    .string()
    .required('Sorry, this is required'),
  email: Yup
    .string()
    .required('Sorry, this is required')
    .email('Invalid email format')
})}

We see from the code above that we can target specific properties that we want to validate in the validationSchema, which in turn gives us access to several methods that we can use for validation. The strings provided in some of the methods are the error messages that will be displayed if a particular input field fails a validation.

Now we will need to add some logic to show the error message on our form component when an input field fails a validation.

<div className="container">

  <div className="col-md-12 mt-5">
    <form onSubmit={handleSubmit}>
      <h4 className="mb-3">Personal information</h4>

      <div className="row">
        <div className="col-md-6 mb-3">
          <label htmlFor="firstname">First name</label>
          <input 
            type="text" 
            className="form-control" 
            id="firstname" 
            name="firstname" 
            value={values.firstname}
            onChange={handleChange}
            onBlur={handleBlur}
          />
          {errors.firstname && touched.firstname ?
            <span style={{color: 'red'}}>
              {errors.firstname}
            </span>
          : null}      
        </div>
        <div className="col-md-6 mb-3">
          <label htmlFor="lastname">Last name</label>
          <input 
            type="text" 
            className="form-control" 
            id="lastname" 
            name="lastname" 
            value={values.lastname}
            onChange={handleChange}
            onBlur={handleBlur}
          />
          {errors.lastname && touched.lastname ?
            <span style={{color: 'red'}}>
              {errors.lastname}
            </span>
          : null} 
        </div>
      </div>

      <div className="mb-3">
        <label htmlFor="email">Email</label>
        <input 
          type="email" 
          className="form-control" 
          id="email" 
          name="email" 
          placeholder="[email protected]" 
          value={values.email}
          onChange={handleChange}
          onBlur={handleBlur}
        />
        {errors.email && touched.email ?
          <span style={{color: 'red'}}>
            {errors.firstname}
          </span>
        : null} 
      </div>

      <div className="row">
        <div className="col-md-5 mb-3">
          <label htmlFor="country">Country</label>
          <select 
            className="custom-select d-block w-100" 
            id="country" 
            name="country" 
            value={values.country}
            onChange={handleChange}
          >
            <option value="">Choose...</option>
            <option value="NIG">Nigeria</option>
            <option value="GH">Ghana</option>
            <option value="SA">South Africa</option>
          </select>        
        </div>
        <div className="col-md-4 mb-3">
          <label htmlFor="state">State</label>
          <select 
            className="custom-select d-block w-100" 
            id="state" 
            name="state" 
            value={values.state}
            onChange={handleChange}
          >
            <option value="">Choose...</option>
            <option value="lagos">Lagos</option>
            <option value="east legion">East Legion</option>
            <option value="cape town">Cape Town</option>
          </select>             
        </div>
        <div className="col-md-3 mb-3">
          <label htmlFor="zip">Zip</label>
          <input 
            type="text" 
            className="form-control" 
            id="zip" 
            name="zip" 
            value={values.zip}
            onChange={handleChange}
          />
        </div>
      </div>

      <hr className="mb-4"/>
      <button className="btn btn-primary btn-lg btn-block" type="submit">
        Submit
      </button>
    </form>
  </div>

</div>

We get access to the error messages passed into the methods in the validationSchema from the errors object. To display the error message, errors.firstname checks if there was an error when validating the firstname input field and touched.firstname checks if the firstname input field was accessed or clicked on by the user. If both conditions passes, we display an error below the input field, else, no error is displayed.

The final code when we pass the validationSchema as a property to the Formik looks like this:

const UserForm = () => {
    return (
      <Formik
        initialValues={{
          firstname: ''
          lastname: ''
          email: ''
          country: ''
          state: ''
          zip: '' 
        }}
        validationSchema = {Yup.object({
          firstname: Yup
            .string()
            .required('Sorry, this is required')
            .max(5, 'Sorry, name is too long'),
          lastname: Yup
            .string()
            .required('Sorry, this is required'),
          email: Yup
            .string()
            .required('Sorry, this is required')
            .email('Invalid email format')
        })}
        onSubmit={() => {
         console.log('form submitted')
        }}
      >
        { ({
          values,
          errors,
          touched,
          handleChange,
          handleBlur,
          handleSubmit
        }) => (
        <div className="container">

          <div className="col-md-12 mt-5">
            <form onSubmit={handleSubmit}>
              <h4 className="mb-3">Personal information</h4>

              <div className="row">
                <div className="col-md-6 mb-3">
                  <label htmlFor="firstname">First name</label>
                  <input 
                    type="text" 
                    className="form-control" 
                    id="firstname" 
                    name="firstname" 
                    value={values.firstname}
                    onChange={handleChange}
                    onBlur={handleBlur}
                  />
                  {errors.firstname && touched.firstname ?
                    <span style={{color: 'red'}}>
                      {errors.firstname}
                    </span>
                  : null} 
                </div>
                <div className="col-md-6 mb-3">
                  <label htmlFor="lastname">Last name</label>
                  <input 
                    type="text" 
                    className="form-control" 
                    id="lastname" 
                    name="lastname" 
                    value={values.lastname}
                    onChange={handleChange}
                    onBlur={handleBlur}
                  />
                  {errors.lastname && touched.lastname ?
                    <span style={{color: 'red'}}>
                      {errors.lastname}
                    </span>
                  : null} 
                </div>
              </div>

              <div className="mb-3">
                <label htmlFor="email">Email</label>
                <input 
                  type="email" 
                  className="form-control" 
                  id="email" 
                  name="email" 
                  placeholder="[email protected]" 
                  value={values.email}
                  onChange={handleChange}
                  onBlur={handleBlur}
                />
                {errors.email && touched.email ?
                  <span style={{color: 'red'}}>
                    {errors.email}
                  </span>
                : null} 
              </div>

              <div className="row">
                <div className="col-md-5 mb-3">
                  <label htmlFor="country">Country</label>
                  <select 
                    className="custom-select d-block w-100" 
                    id="country" 
                    name="country" 
                    value={values.country}
                    onChange={handleChange}
                  >
                    <option value="">Choose...</option>
                    <option value="NIG">Nigeria</option>
                    <option value="GH">Ghana</option>
                    <option value="SA">South Africa</option>
                  </select>        
                </div>
                <div className="col-md-4 mb-3">
                  <label htmlFor="state">State</label>
                  <select 
                    className="custom-select d-block w-100" 
                    id="state" 
                    name="state" 
                    value={values.state}
                    onChange={handleChange}
                  >
                    <option value="">Choose...</option>
                    <option value="lagos">Lagos</option>
                    <option value="east legion">East Legion</option>
                    <option value="cape town">Cape Town</option>
                  </select>             
                </div>
                <div className="col-md-3 mb-3">
                  <label htmlFor="zip">Zip</label>
                  <input 
                    type="text" 
                    className="form-control" 
                    id="zip" 
                    name="zip" 
                    value={values.zip}
                    onChange={handleChange}
                  />
                </div>
              </div>

              <hr className="mb-4"/>
              <button className="btn btn-primary btn-lg btn-block" type="submit">
               Submit
              </button>
            </form>
          </div>

        </div>
        ) }
      </Formik>
    )
}

That is it for this article. As you can see, with Formik and Yup we are able to avoid unnecessary boilerplate code. Formik handles the validation by default. So as you are entering values and clicking submit, it is running the validation and won't submit until all form values can pass.

Fomik makes it easy to access and update form values. Use handleChange to handle update and the values object holds all the current values. Same with the errors object we use to display the error messages for individual fields.

Above, is a demo on how our form should look and work.

Hope you find this article as useful as the first. Thanks and have a nice read.

25