Form Validation using Formik in ReactJS

This guide provides a comprehensive introduction to form validation using Formik in ReactJS. It covers setting up Formik, basic form creation, state management, validation methods, and best practices for handling forms and validation in ReactJS applications.

Introduction to Formik

Formik is a powerful library designed to simplify form building and validation in React applications. It abstracts the repetitive and boilerplate-heavy parts of form management, making it easier to focus on building your application's core features. Formik handles form state management, submission, and validation, which can otherwise be quite complex to manage manually, especially in large applications.

What is Formik?

To put it simply, Formik is a React library that takes care of form state, validation, and form submission. It simplifies the process by providing a collection of reusable components and hooks that can be used to handle various types of forms. This allows you to build forms more efficiently and with less code compared to handling everything from scratch.

Setting Up Formik in Your Project

Before we dive into the specifics of using Formik, we need to set it up in our React project. Assuming you already have a React project set up, here’s how you can add Formik:

  1. First, you need to install Formik. You can do this using npm or yarn. For this guide, we'll use npm:
    npm install formik
    
  2. If you plan to use Yup for validation, you'll need to install it as well:
    npm install yup
    

Now that we haveFormik and Yup installed, let's move on to creating a simple form with Formik.

Basic Form Setup with Formik

Let's start by creating a basic form using Formik. We'll create a simple login form with fields for an email and a password.

Creating a Simple Form

First, import the necessary components from Formik:

// Import necessary components from Formik
import { Formik, Form, Field, ErrorMessage } from 'formik';

We need to define a simple form component. Here’s how you can create a login form using Formik:

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';

function LoginForm() {
  return (
    <Formik
      initialValues={{ email: '', password: '' }}
      onSubmit={(values, actions) => {
        console.log('Form values:', values);
        actions.setSubmitting(false); // This line prevents onSubmit to be called more than once
      }}
    >
      {({ isSubmitting }) => (
        <Form>
          <div>
            <label htmlFor="email">Email Address</label>
            <Field type="email" name="email" />
            <ErrorMessage name="email" component="div" />
          </div>
          <div>
            <label htmlFor="password">Password</label>
            <Field type="password" name="password" />
            <ErrorMessage name="password" component="div" />
          </div>
          <button type="submit" disabled={isSubmitting}>
            Login
          </button>
        </Form>
      )}
    </Formik>
  );
}

export default LoginForm;

In the code above:

  • initialValues sets the initial state of the form.
  • onSubmit is a function that gets called when the form is submitted.
  • Form is a Formik component that wraps the form elements.
  • Field is a Formik component used to connect input elements to the form state.
  • ErrorMessage is a Formik component that displays error messages for fields.

Handling Form Submission

The onSubmit function in the example above is where you would handle the submission of your form, such as sending the data to a server. The Formik component provides an actions object which includes several methods like setSubmitting that can be used to control the form's submission state.

Understanding Form State in Formik

Formik manages the form's state, including input values and errors, making it easier for you. Let's explore more about how Formik manages state.

Maintaining State with Formik

Formik uses the initialValues prop to set the initial state of the form. As the user types into the form fields, Formik automatically updates the form state. This is handled by the Field component, which automatically connects input elements to the form state.

Accessing Form Values and Errors

Formik provides a way to access form values and errors through the Formik component's render props. Let's modify our login form to log the form values and errors every time they change:

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';

function LoginForm() {
  return (
    <Formik
      initialValues={{ email: '', password: '' }}
      onSubmit={(values, actions) => {
        console.log('Form values:', values);
        actions.setSubmitting(false);
      }}
    >
      {({ values, errors, handleChange, handleBlur, isSubmitting }) => (
        <Form>
          <div>
            <label htmlFor="email">Email Address</label>
            <Field type="email" name="email" onChange={handleChange} onBlur={handleBlur} />
            {errors.email && <div>{errors.email}</div>}
          </div>
          <div>
            <label htmlFor="password">Password</label>
            <Field type="password" name="password" onChange={handleChange} onBlur={handleBlur} />
            {errors.password && <div>{errors.password}</div>}
          </div>
          <button type="submit" disabled={isSubmitting}>
            Login
          </button>
          <pre>{JSON.stringify(values, null, 2)}</pre>
          <pre>{JSON.stringify(errors, null, 2)}</pre>
        </Form>
      )}
    </Formik>
  );
}

export default LoginForm;

In the code above, we access values and errors through the Formik component's render props. We also use handleChange and handleBlur to handle changes and focus events for form inputs.

Basic Validation with Formik

Now that we understand how Formik handles form state, let's dive into validation.

Writing Simple Validation Functions

Formik allows you to write validation functions that are attached to the form. These functions can be used to enforce rules on the input fields.

Attaching Validation Functions to Form

Let's attach a simple validation function to our login form. We'll add a basic validation to ensure that the email and password fields are not empty:

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';

function loginForm() {
  return (
    <Formik
      initialValues={{ email: '', password: '' }}
      onSubmit={(values, actions) => {
        console.log('Form values:', values);
        actions.setSubmitting(false);
      }}
      validate={(values) => {
        const errors = {};
        if (!values.email) {
          errors.email = 'Email is required';
        }
        if (!values.password) {
          errors.password = 'Password is required';
        }
        return errors;
      }}
    >
      {({ values, errors, handleChange, handleBlur, isSubmitting }) => (
        <Form>
          <div>
            <label htmlFor="email">Email Address</label>
            <Field type="email" name="email" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="email" component="div" />
          </div>
          <div>
            <label htmlFor="password">Password</label>
            <Field type="password" name="password" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="password" component="div" />
          </div>
          <button type="submit" disabled={isSubmitting}>
            Login
          </button>
          <pre>{JSON.stringify(errors, null, 2)}</pre>
        </Form>
      )}
    </Formik>
  );
}

export default LoginForm;

In the code above, we added a validate prop to the Formik component. This function checks if the email and password fields are empty and sets corresponding error messages.

Error Messages and User Feedback

Error messages are crucial for guiding users to correct form errors. Formik provides a way to display error messages and customize their appearance.

Displaying Error Messages

Formik has a built-in ErrorMessage component that can be used to display error messages for fields with validation errors. We've already seen this in the previous example.

Customizing Error Message Styles

You can customize the styles of error messages using CSS. Let's add some basic styling to our form:

.error {
  color: red;
  font-size: 0.8em;
}

Now, we'll update our LoginForm to use this CSS class:

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import './LoginForm.css'; // Make sure to import your CSS file

function LoginForm() {
  return (
    <Formik
      initialValues={{ email: '', password: '' }}
      onSubmit={(values, actions) => {
        console.log('Form values:', values);
        actions.setSubmitting(false);
      }}
      validate={(values) => {
        const errors = {};
        if (!values.email) {
          errors.email = 'Email is required';
        }
        if (!values.password) {
          errors.password = 'Password is required';
        }
        return errors;
      }}
    >
      {({ values, errors, handleChange, handleBlur, isSubmitting }) => (
        <Form>
          <div>
            <label htmlFor="email">Email Address</label>
            <Field type="email" name="email" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="email" component="div" className="error" />
          </div>
          <div>
            <label htmlFor="password">Password</label>
            <Field type="password" name="password" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="password" component="div" className="error" />
          </div>
          <button type="submit" disabled={isSubmitting}>
            Login
          </button>
        </Form>
      )}
    </Formik>
  );
}

export default LoginForm;

Advanced Validation Techniques

Basic validation is a good start, but for more complex validation, Formik can be integrated with Yup, a powerful schema description language and data validator for JavaScript objects.

Validation Using Yup Schema

Yup provides a powerful way to define validation schemas, making it easy to validate nested objects, arrays, and complex types.

Installing Yup

You can install Yup using npm:

npm install yup

Creating Validation Schema

Now we will create a validation schema using Yup. We'll define rules for our email field to ensure it's a valid email and the password field to have a minimum length:

import * as Yup from 'yup';

const loginSchema = Yup.object().shape({
  email: Yup.string().email('Invalid email address').required('Email is required'),
  password: Yup.string().min(6, 'Password must be 6 or more characters').required('Password is required'),
});

In the code above, we define a Yup schema with rules for the email and password fields.

Integrating Schema with Formik

Now let's integrate this schema with our LoginForm:

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import './LoginForm.css'; // Make sure to import your CSS file

const loginSchema = Yup.object().shape({
  email: Yup.string().email('Invalid email address').required('Email is required'),
  password: Yup.string().min(6, 'Password must be 6 or more characters').required('Password is required'),
});

function LoginForm() {
  return (
    <Formik
      initialValues={{ email: '', password: '' }}
      validationSchema={loginSchema}
      onSubmit={(values, actions) => {
        console.log('Form values:', values);
        actions.setSubmitting(false);
      }}
    >
      {({ values, errors, handleChange, handleBlur, isSubmitting }) => (
        <Form>
          <div>
            <label htmlFor="email">Email Address</label>
            <Field type="email" name="email" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="email" component="div" className="error" />
          </div>
          <div>
            <label htmlFor="password">Password</label>
            <Field type="password" name="password" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="password" component="div" className="error" />
          </div>
          <button type="submit" disabled={isSubmitting}>
            Login
          </button>
        </Form>
      )}
    </Formik>
  );
}

export default LoginForm;

In the code above, we added a validationSchema prop to the Formik component, which refers to the Yup schema we created.

Validation Methods

Formik provides multiple methods for validation, such as validate, validateField, validateForm, onChange, and onBlur.

Validation with validate Function

We've already seen the validate function in action. It is used to define validation logic for the entire form.

Validation with validateField and validateForm

Formik also provides validateField and validateForm methods, which can be used to validate individual fields or the entire form programmatically. Here’s how you can use validateField to validate the email field:

// Inside your Formik component
{({ values, errors, handleChange, handleBlur, isSubmitting, validateField }) => (
  <Form>
    <div>
      <label htmlFor="email">Email Address</label>
      <Field type="email" name="email" onChange={handleChange} onBlur={handleBlur} />
      <ErrorMessage name="email" component="div" className="error" />
      <button type="button" onClick={() => validateField('email')}>
        Validate Email
      </button>
    </div>
    {/* Rest of the form... */}
  </Form>
)}

Validation with onChange and onBlur

Formik also supports validation on change and blur events. This allows you to validate fields as the user types or when they move away from a field. This gives immediate feedback to the user.

Handling Validation in Nested Forms

Formik can handle both simple and complex nested forms, including forms with arrays.

Simple Nested Forms

Let’s create a simple nested form with a user’s first and last name:

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import './LoginForm.css';

const userSchema = Yup.object().shape({
  firstName: Yup.string().required('First Name is required'),
  lastName: Yup.string().required('Last Name is required'),
});

function UserForm() {
  return (
    <Formik
      initialValues={{ firstName: '', lastName: '' }}
      validationSchema={userSchema}
      onSubmit={(values, actions) => {
        console.log('Form values:', values);
        actions.setSubmitting(false);
      }}
    >
      {({ values, errors, handleChange, handleBlur, isSubmitting }) => (
        <Form>
          <div>
            <label htmlFor="firstName">First Name</label>
            <Field type="text" name="firstName" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="firstName" component="div" className="error" />
          </div>
          <div>
            <label htmlFor="lastName">Last Name</label>
            <Field type="text" name="lastName" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="lastName" component="div" className="error" />
          </div>
          <button type="submit" disabled={isSubmitting}>
            Submit
          </button>
        </Form>
      )}
    </Formik>
  );
}

export default UserForm;

Complex Nested Forms with Arrays

Formik can also handle arrays in forms. Let’s create a form where a user can add multiple phone numbers:

import React from 'react';
import { Formik, Form, Field, ErrorMessage, FieldArray } from 'formik';
import * as Yup from 'yup';
import './LoginForm.css';

const userSchema = Yup.object().shape({
  firstName: Yup.string().required('First Name is required'),
  lastName: Yup.string().required('Last Name is required'),
  phoneNumbers: Yup.array()
    .of(Yup.string().test('check-phone', 'Invalid phone number', (value) => !value || /^\d+$/.test(value))),
});

function ComplexNestedForm() {
  return (
    <Formik
      initialValues={{ firstName: '', lastName: '', phoneNumbers: [''] }}
      validationSchema={userSchema}
      onSubmit={(values, actions) => {
        console.log('Form values:', values);
        actions.setSubmitting(false);
      }}
    >
      {({ values, errors, handleChange, handleBlur, isSubmitting }) => (
        <Form>
          <div>
            <label htmlFor="firstName">First Name</label>
            <Field type="text" name="firstName" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="firstName" component="div" className="error" />
          </div>
          <div>
            <label htmlFor="lastName">Last Name</label>
            <Field type="text" name="lastName" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="lastName" component="div" className="error" />
          </div>
          <FieldArray name="phoneNumbers">
            {({ push, remove, form }) => (
              <div>
                {form.values.phoneNumbers.length > 0 &&
                  form.values.phoneNumbers.map((phone, index) => (
                    <div key={index}>
                      <Field type="text" name={`phoneNumbers.${index}`} onChange={handleChange} onBlur={handleBlur} />
                      <button type="button" onClick={() => remove(index)}>-</button>
                      <ErrorMessage name={`phoneNumbers.${index}`} component="div" className="error" />
                    </div>
                  ))}
                <button type="button" onClick={() => push('')}>
                  Add Phone Number
                </button>
              </div>
            )}
          </FieldArray>
          <button type="submit" disabled={isSubmitting}>
            Submit
          </button>
        </Form>
      )}
    </Formik>
  );
}

export default ComplexNestedForm;

In the code above, we use FieldArray to handle arrays in our form. The push function is used to add a new phone number, and the remove function is used to remove an existing phone number.

Managing Formik State and Props

Formik provides several props that can be used to manage form state and handle form actions.

Props Provided by Formik

Formik provides several useful props like values, errors, handleChange, handleBlur, isSubmitting, etc. that can be used to manage form state and handle form actions within the Formik component's render props.

State Included in Formik

Formik includes state properties that are useful for managing form state, such as form values, errors, and submission state. These are accessible through the render props of the Formik component.

Conditional Validation

Conditional validation can be implemented in Formik by updating the validation function or schema based on the form state.

Implementing Conditional Logic in Validation

Let's add a conditional validation where the password must be 8 characters long if the email domain is 'example.com':

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import './LoginForm.css';

const userSchema = Yup.object().shape({
  firstName: Yup.string().required('First Name is required'),
  lastName: Yup.string().required('Last Name is required'),
  email: Yup.string().email('Invalid email address').required('Email is required'),
  password: Yup.string().test(
    'password-validation',
    'Password must be 8 characters long if email is example.com',
    function (value) {
      const emailDomain = this.parent.email.split('@')[1];
      if (emailDomain === 'example.com' && value.length < 8) {
        return false;
      }
      return true;
    }
  ),
});

function ConditionalForm() {
  return (
    <Formik
      initialValues={{ firstName: '', lastName: '', email: '', password: '' }}
      validationSchema={userSchema}
      onSubmit={(values, actions) => {
        console.log('Form values:', values);
        actions.setSubmitting(false);
      }}
    >
      {({ values, errors, handleChange, handleBlur, isSubmitting }) => (
        <Form>
          <div>
            <label htmlFor="firstName">First Name</label>
            <Field type="text" name="firstName" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="firstName" component="div" className="error" />
          </div>
          <div>
            <label htmlFor="lastName">Last Name</label>
            <Field type="text" name="lastName" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="lastName" component="div" className="error" />
          </div>
          <div>
            <label htmlFor="email">Email Address</label>
            <Field type="email" name="email" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="email" component="div" className="error" />
          </div>
          <div>
            <label htmlFor="password">Password</label>
            <Field type="password" name="password" onChange={handleChange} onBlur={handleBlur} />
            <ErrorMessage name="password" component="div" className="error" />
          </div>
          <button type="submit" disabled={isSubmitting}>
            Submit
          </button>
        </Form>
      )}
    </Formik>
  );
}

export default ConditionalForm;

Reactivating Validation

Sometimes, you might need to trigger validation manually, such as when the form is re-enabled or when you want to check for errors programmatically.

Re-enabling Validation

If you disable form validation and want to re-enable it, you can do so by controlling the validate or validationSchema prop.

Triggering Validation Manually

You can manually trigger validation by using the validateField or validateForm methods provided by Formik:

import React from 'react';
import { Formik, Form, Field, ErrorMessage, FieldArray } from 'formik';
import * as Yup from 'yup';
import './LoginForm.css';

const userSchema = Yup.object().shape({
  firstName: Yup.string().required('First Name is required'),
  lastName: Yup.string().required('Last Name is required'),
});

function TriggerValidationForm() {
  return (
    <Formik
      initialValues={{ firstName: '', lastName: '' }}
      validationSchema={userSchema}
      onSubmit={(values, actions) => {
        console.log('Form values:', values);
        actions.setSubmitting(false);
      }}
    >
      {({ values, isSubmitting, validateField, validateForm }) => (
        <Form>
          <div>
            <label htmlFor="firstName">First Name</label>
            <Field type="text" name="firstName" />
            <button type="button" onClick={() => validateField('firstName')}>Validate First Name</button>
            <ErrorMessage name="firstName" component="div" className="error" />
          </div>
          <div>
            <label htmlFor="lastName">Last Name</label>
            <Field type="text" name="lastName" />
            <button type="button" onClick={() => validateField('lastName')}>Validate Last Name</button>
            <ErrorMessage name="lastName" component="div" className="error" />
          </div>
          <div>
            <button type="button" onClick={validateForm}>Validate Form</button>
          </div>
          <button type="submit" disabled={isSubmitting}>
            Submit
          </button>
        </Form>
      )}
    </Formik>
  );
}

export default TriggerValidationForm;

In the code above, we added buttons to manually trigger validation for individual fields or the entire form.

Final Steps and Tips

Summary of Key Points

  • Formik simplifies form building and validation in ReactJS.
  • Use Formik components to handle form elements and validation.
  • Use Yup for complex validation schemas.
  • Leverage Formik's props and methods to handle form state and validation actions.
  • Use Yup for complex validation schemas to keep your validation logic clean and maintainable.
  • Validate fields on change or blur events for real-time feedback.
  • Handle form submission and validation errors gracefully.
  • Keep your forms as simple as possible to avoid unnecessary complexity.

Formik makes form building and validation in ReactJS much more manageable, and with the use of Yup, you can create powerful and flexible validation schemas. By following the best practices outlined in this guide, you can create robust and user-friendly forms in your React applications.