Perhaps one of the most annoying tasks in React is creating forms and validating them, especially if you're doing it without using any libraries. You'll have to manage the states, values, and validation of all inputs.

Formik is a React and React Native library that helps you create forms in React "without the tears". You can pair Formik with validation libraries like Yup to make the process even simpler.

In this tutorial, you'll learn how creating and validating forms can be simpler in React using Formik and Yup. You'll create a simple form with different types of fields and see the different ways you can validate that form.

You can find the code for this tutorial in this GitHub repository.

Project Setup

In this section, you'll set up your website using Create React App (CRA) and install some dependencies for the sake of the tutorial. If you already have a website set up you can skip this part.

In your terminal, run the following command to create a new React website with CRA:

npx create-react-app react-forms

I'm calling the website react-forms but you can change it to whatever you want.

Once the installation is done, change to the newly created directory:

cd react-forms

Then, install Tailwind CSS to add some styling to your website:

npm install -D tailwindcss postcss autoprefixer

To set up Tailwind CSS create the file tailwind.config.js with the following content:

module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

And replace the content of src/index.css with the following:

@tailwind base;
@tailwind components;
@tailwind utilities;

Create the Form with Formik

You'll now use Formik to create a form. First, install Formik:

npm i formik

Replace the content of src/App.js with the following:

import { useFormik } from 'formik';

function App() {
    const professions = ['Developer', 'Designer', 'Other'];
    //TODO create formik instance
    
    return (
    	<div className="bg-blue-300 min-w-screen min-h-screen overflow-x-hidden">
        </div>
    );
}

export default App;

All you did here is create the component App which does nothing special at the moment.

Notice how you import the useFormik hook at the beginning of the file. You'll use this hook to create a Formik instance with all the states and helpers you'll need.

The useFormik hook accepts as a parameter an object of configurations. These configurations can be used to modify and shape your form as you need.

In this tutorial, you'll use the pass the following properties in the object:

  1. initialValues: includes the form fields and their initial values.
  2. validationSchema: A Yup schema to validate the fields. You'll use this in the next section.
  3. onSubmit: a function to execute when the form is submitted.

Replace the TODO in the App component with the following:

const formik = useFormik({
    initialValues: {
      name: '',
      email: '',
      profession: professions[0],
      age: '',
    },
    onSubmit: function (values) {
      alert(`You are registered! Name: ${values.name}. Email: ${values.email}. Profession: ${values.profession}. 
        Age: ${values.age}`);
    }
  })

As you can see, you set the value of the property initialValues to an object. This object's keys are the names of the fields in the form. Their values are the initial value.

In the onSubmit function, you receive the values object as a parameter. Here you can access the values and use them to save them in the database or send them to a server. For the sake of this tutorial, you just print them out in an alert.

Note that the onSubmit function is only executed once the form is validated. So, you don't need to perform any validation inside this function.

Now, you can use the formik variable to create a form, link its fields to the fields you defined in useFormik, link the validation, and link the submit handler.

formik includes the following properties among others:

  1. handleSubmit: the submit function that should be called when the form is submitted. This is usually assigned to the onSubmit event handler of form elements.
  2. errors: An object that has the field names as properties and the value of each is the error message resulted from validating that field if there are any errors.
  3. touched: An object that has the field names as properties and the value is a boolean indicating whether the user has interacted with the field or not.
  4. values: An object that has the field names as properties and the value of each is the current value of that field. It's usually used to set the value property of input elements.
  5. handleChange: A function that should be used as the handler of the change event of input elements. It's passed as the value of the onChange prop of elements.
  6. handleBlur: A function that should be used as the handler of the blur event of input elements. It's passed as the value of the onBlur prop of elements.

Replace the return statement in App with the following:

return (
    <div className="bg-blue-300 min-w-screen min-h-screen overflow-x-hidden">
      <form onSubmit={formik.handleSubmit} className="max-w-lg mx-auto bg-white rounded shadow-lg mt-7 p-3">
      <h1 className='text-3xl mb-3 text-center'>Register</h1>
        <div className='mb-4'>
          <label for="name">Full Name</label>
          <input type="text" name="name" id="name" 
            className={`block w-full rounded border py-1 px-2 ${formik.touched.name && formik.errors.name ? 'border-red-400' : 'border-gray-300'}`}
            onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.name} />
          {formik.touched.name && formik.errors.name && (
            <span className='text-red-400'>{formik.errors.name}</span>
          )}
        </div>
        <div className='mb-4'>
          <label for="email">Email</label>
          <input type="email" name="email" id="email"
            className={`block w-full rounded border py-1 px-2 ${formik.touched.email && formik.errors.email ? 'border-red-400' : 'border-gray-300'}`}
            onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.email} />
          {formik.touched.email && formik.errors.email && (
            <span className='text-red-400'>{formik.errors.email}</span>
          )}
        </div>
        <div className='mb-4'>
          <label for="profession">Profession</label>
          <select name="profession" id="profession"
            className={`block w-full rounded border py-1 px-2 ${formik.touched.profession && formik.errors.profession ? 'border-red-400' : 'border-gray-300'}`}
            onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.profession} >
            {professions.map((profession, index) => (
              <option value={profession} key={index}>{profession}</option>
            ))}
          </select>
          {formik.touched.profession && formik.errors.profession && (
            <span className='text-red-400'>{formik.errors.profession}</span>
          )}
        </div>
        <div className='mb-4'>
          <label for="age">Age</label>
          <input type="number" name="age" id="age"
            className={`block w-full rounded border py-1 px-2 ${formik.touched.age && formik.errors.age ? 'border-red-400' : 'border-gray-300'}`}
            onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.age} />
          {formik.touched.age && formik.errors.age && (
            <span className='text-red-400'>{formik.errors.age}</span>
          )}
        </div>
        <div className='text-center'>
          <button className='bg-blue-500 rounded p-3 text-white' type='submit'>Submit</button>
        </div>
      </form>
    </div>
  );

Notice how you used all the properties in the formik variable mentioned earlier.

Test it Out

The form is now created and ready to be used, even if there's no validation yet.

To test it out, run the server using the following command:

npm start

You can then open the website at localhost:3000 (default port). If you open the website, you'll see the form with 4 fields.

You can try and fill out the form. As currently there's no validation, you can fill out (or not) values as you want and click Submit. An alert will show with the values you entered.

Add Validation with Yup

In this section, you'll add validation to the form using Yup.

First, you need to install Yup. Run the following in your terminal:

npm i yup

Yup has a lot of methods and validation rules you can use. The way it works with Formik is you need to create a validation schema and pass it to useFormik as a value to the property validationSchema.

Yup validation schemas are created using Yup.object method which takes as a parameter an object. This object has the field names as properties and their values are validation rules from the Yup library.

Import Yup at the beginning of src/App.js:

import * as Yup from 'yup';

Then, add the property validationSchema to the object passed to useFormik with the following value:

const formik = useFormik({
    ...,
    validationSchema: Yup.object({
      name: Yup.string()
              .label('Full Name')
              .required(),
      email: Yup.string()
              .email()
              .required(),
      profession: Yup.string()
                  .oneOf(professions, 'The profession you chose does not exist'),
      age: Yup.number()
            .min(15, 'You need to be older than 15 to register')
            .required()
    })
  })

You add the following validation rules:

  1. name: Should be a string and is required. You also use the label method to ensure that when the error message is shown it refers to the field as "Full Name". By default, the fields are referred to by the field name, which in this case is name.
  2. email: Should be a string, an email, and required.
  3. profession: Should be a string and one of the values in the professions array. You also pass a message as a second parameter to oneOf which will be the message that shows in case there's an error. It's also required.
  4. age: Should be a number and at least 15. If the age is less than 15, the message "You need to be older than 15 to register" will show. It's also required.

Test it Out

Let's test it out. Run the server again if it's not running and open the website. If you enter values now that don't comply with the rules you set in the validation schema, an error will show in red and you won't be able to submit the form before resolving the errors.

If you all values are valid, then the form will be submitted and an alert will show.

Custom Validation Rules

Although Yup has helpful validation rules that you can use with most common cases, a lot of times you might need a custom validation rule. You can use the test function to add a custom rule.

In this section, you'll add a rule to make sure that the name field has both first and last name.

Change the name property inside the validationSchema to the following:

const formik = useFormik({
    ...,
    validationSchema: Yup.object({
      name: Yup.string()
              .label('Full Name')
              .required()
              .test('is-full-name', 'Please enter both your first and last name', function (value) {
                const nameArr = value.split(" ");
                return nameArr.length >= 2;
              }),
      ...
    })
  })

The first parameter is the name of the custom rule. The second parameter is the message to show in case the field is invalid.

The third parameter is the function that determines whether the field is valid or not. It should return a boolean value. If the value is true, then the field is valid. Otherwise, it's invalid.

You validate the name field to contain both first and last names by just splitting it  on the space delimiter which will return an array. You then check the array length. If it's at least 2, then the field is valid. Otherwise it's invalid.

Test it Out

Run the server again now and go to the website. If you enter one word in the Full Name field you'll see an error.

You'll need to enter at least two words for the field to be valid.

Conclusion

In this tutorial you learned how to use Formik and Yup in React. You can use these two libraries to create forms, validate them, and handle their submission. Using these two libraries makes creating forms in React easier and less stressful.