Mastering Form Validation in React with Formik and Yup (with TypeScript)
Introduction
Form validation is an important part of developing robust and user-friendly web applications. In React, developers frequently rely on powerful third-party libraries to simplify validation and provide a consistent user experience. Formik and Yup, two popular libraries, work in perfect harmony to provide an elegant and efficient solution for form validation in React. In this comprehensive guide, we will delve into the process of implementing form validation with Formik and Yup while leveraging TypeScript's additional benefits.
Why do I need another library?
While it is true that you can write your own form validation logic without relying on third-party libraries like Formik and Yup, consider the following form:
You practically have to handle the validation of every input field, state, error handling, error message, and so on. Sounds tedious, does not it? Do not worry, you are not required to respond; I have been there and understand. Do not get me wrong, it is doable, but why put yourself through all that stress when there is a tool available that can do the majority of the work?
Using these libraries can provide substantial benefits while saving you time and effort. Here are a few reasons why you should use Formik and Yup rather than write your own validation:
- Simplicity and Convenience: Formik and Yup provide a simple and declarative way to handle form validation in React. They abstract away much of the boilerplate code and provide a streamlined API for managing form state, handling validation, and displaying validation errors. This can save you from having to write complex validation logic from scratch and make your code more maintainable and readable.
- Validation Schema Definition: Yup, specifically, it offers a powerful validation schema definition syntax. It allows you to easily define complex validation rules, such as string, number, date, and object validation. Yup comes with a plethora of built-in validation methods, including required fields, min/max length, pattern matching, and more. This eliminates the need to write all of the validation logic manually and makes your code more concise.
- Built-in Error Handling: Formik integrates seamlessly with Yup, enabling automatic error handling and displaying validation errors in your form. With Formik, you can easily access error messages for each field and display them to the user. It handles the validation logic behind the scenes and provides a clean API for working with form errors.
- Form State Management: Formik takes care of managing form state, including field values, touched and dirty states, form submission, and resetting the form. It provides hooks and utilities for accessing and manipulating the form state, simplifying the overall form management process. This is especially useful when dealing with complicated forms with multiple fields and validation requirements.
- Community Support and Ecosystem: Formik and Yup are widely adopted libraries in the React community. Because these libraries have a large user base, you can find extensive documentation, tutorials, and community support when working with them. Furthermore, Formik has a robust ecosystem of extensions, plugins, and integrations to enhance your form development experience.
While it is possible to write your own form validation logic, leveraging the capabilities of Formik and Yup can save you time, improve code maintainability, and provide a more robust and user-friendly form validation solution. These libraries have been thoroughly tested and optimized for handling complex validation scenarios, allowing you to concentrate on developing your application's core features.
For the demo, we will build a basic registration form with validations and basic CSS styling (see the image below) nothing too fancy.
Without wasting much time, let’s get to work.
Prerequisites
Before we dive into the implementation, let’s ensure we have everything set up correctly. To follow along with this tutorial, you’ll need the following:
- Basic knowledge of React and TypeScript.
- A working React project with TypeScript support.
- Installation of required dependencies:
- React: ^16.8.0 or later
- Formik: ^2.2.0 or later
- Yup: ^0.32.1 or later
Setting up the Project
To begin, let’s create a new React project and install the necessary dependencies. Open your terminal and execute the following commands:
npx create-react-app form-validation-demo --template typescript
cd form-validation-demo
npm install formik yup
This will create a new React project with TypeScript support and install the Formik and Yup libraries.
Creating a Form Component
In this section, we will create a form component that handles form input and validation. Create a new file called Form.tsx
in the src
folder and add the following code:
import React from 'react';
interface FormValues {
name: string;
email: string;
password: string;
confirmPassword: string;
}
const Form: React.FC = () => {
const initialValues: FormValues = {
name: '',
email: '',
password: '',
confirmPassword: '',
};
return (
<div>
<h1>Form Validation Example</h1>
<form>
{/* Form fields */}
</form>
</div>
);
};
export default Form;
Defining the Validation Schema with Yup
In this section, we will use Yup to define the validation schema for our form. Yup offers a powerful and expressive API for defining validation rules for different form fields.
To define the validation schema, import the necessary dependencies at the top of the Form.tsx
file:
import { Formik, Field, ErrorMessage, FormikValues, FormikHelpers } from 'formik';
import * as Yup from 'yup';
Next, within the Form
component, define the validation schema using Yup's object().shape()
method:
const validationSchema = Yup.object().shape({
name: Yup.string().required('Name is required'),
email: Yup.string().email('Invalid email').required('Email is required'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Password is required'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password')], 'Passwords must match')
.required('Confirm Password is required'),
});
Here, we define validation rules for each form field. For example, the name
field is required, the email
field must be a valid email address, the password
field must be at least 6 characters long; and the confirmPassword
field must match the password
field.
Within the Form
component, wrap the form with the Formik
component:
return (
<div>
<h1>Form Validation Example</h1>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
<form>
{/* Form fields */}
</form>
</Formik>
</div>
);
Here, we pass the initialValues
, validationSchema
, and onSubmit
handler to the Formik
component. The initialValues
define the initial state of the form fields, the validationSchema
specifies the validation rules, and the onSubmit
handler is invoked when the form is submitted.
Displaying Validation Errors
We will improve our form in this section by displaying validation errors for each field.
Within the form
element, add the following code for each form field:
<label htmlFor="name">
Name:
<Field type="text" name="name" />
<ErrorMessage name="name" component="div" className="error" />
</label>
Here, we use the Field
component to render the input field and bind it to the corresponding name
field in the initialValues
. The ErrorMessage
component displays the validation error message for the specified field if it fails validation.
To style the error messages, let’s create a new file called Form.css
in the src
folder and add the following CSS code:
.input-err {
border: 1px solid red;
}
.error {
color: red;
font-size: 0.8rem;
margin-top: 0.2rem;
}
The input-err
class add a border of red to the input box when the validation failed and error
to display an error message.
Finally, import the CSS file into the Form.tsx
file:
import './Form.css';
Handling Form Submission
Once the form is validated and the submit button is clicked, you’ll need to handle the form submission. Formik provides an onSubmit
handler that receives the form values and formik helpers to perform actions after successful form submission.
In the Form
component, define the handleSubmit
function to handle the form submission:
const handleSubmit = (
values: FormikValues,
{ setSubmitting }: FormikHelpers<FormValues>
) => {
setTimeout(() => {
console.log(values);
setSubmitting(false);
}, 500);
};
In this example, the handleSubmit
function logs the form values to the console after a short delay of 500ms. The setSubmitting
function is used to indicate that the form submission is complete and enable the form for further interactions.
Below is the demo video of our form validation.
Some Advanced Validation Techniques
Yup offers a wide range of advanced validation techniques that can be incorporated into your form validation process in addition to basic validation rules. Conditional validation, validating arrays, and composing multiple validation rules are some commonly used techniques.
To implement conditional validation, you can use Yup’s when
method. You can use this method to define validation rules based on the values of other fields. For example, let's say you have a field called newsletter
that should only be required if the user selects a specific option. You can define this rule as follows:
validationSchema = Yup.object().shape({
// other field validations...
newsletter: Yup.boolean().when('subscribe', {
is: true,
then: Yup.boolean().oneOf([true], 'You must subscribe to the newsletter'),
}),
});
In this example, the newsletter
field will only be required if the subscribe
field is set to true
.
Custom Validation Functions
While Yup comes with a variety of built-in validation methods, there may be times when you need to implement custom validation logic. To handle complex validation requirements, Yup allows you to define custom validation functions.
To implement a custom validation function, you can use the test
method provided by Yup. This method takes a string identifier and a callback function that performs the validation. For example, let's say you have a field called age
that should only accept values between 18 and 65. You can define a custom validation rule as follows:
validationSchema = Yup.object().shape({
// other field validations...
age: Yup.number().test('age', 'Invalid age', (value) => {
return value >= 18 && value <= 65;
}),
});
In this example, the custom validation function checks if the age
field value is between 18 and 65.
Asynchronous Validation
Asynchronous validation, such as validating against a server endpoint or performing complex asynchronous operations, may be required in some cases. Yup allows you to handle asynchronous validation using the test
method with an asynchronous callback function.
To implement asynchronous validation, you can define an asynchronous callback function and use the resolve
and reject
parameters to handle the validation result. For example, let's say you have a field called username
that needs to be unique and requires an asynchronous validation call to the server. You can define an asynchronous validation rule as follows:
validationSchema = Yup.object().shape({
// other field validations...
username: Yup.string().test('username', 'Username already exists', async (value) => {
return await isUsernameAvailable(value);
}),
});
In this example, the isUsernameAvailable
function is an asynchronous function that checks if the username is available. The custom validation function awaits the asynchronous call and returns true
or false
based on the result.
Disabling the Submit Button
In some cases, you may want to disable the submit button until all form fields are valid. Formik provides a isSubmitting
property that can be used to disable the submit button during form submission.
To disable the submit button until all form fields are valid, you can add the isSubmitting
property to the Formik
component and use it to conditionally disable the button:
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ isSubmitting, handleSubmit }) => (
<form onSubmit={handleSubmit}>
{/* Form fields */}
<button type="submit" disabled={isSubmitting}>Submit</button>
</form>
)}
</Formik>
In this example, the submit button will be disabled when the form is being submitted (isSubmitting
is true
), ensuring that the form is not submitted multiple times.
Here is the entire code.
import React from 'react';
import { Formik, Field, ErrorMessage, FormikValues, FormikHelpers } from 'formik';
import * as Yup from 'yup';
import './Form.css';
interface FormValues {
name: string;
email: string;
password: string;
confirmPassword: string;
}
const validationSchema = Yup.object().shape({
name: Yup.string()
.min(2, 'Name must be minimum 2')
.max(100, 'Name must not be more than 100 characters')
.required('Name is required'),
email: Yup.string().email('Invalid email').required('Email is required'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Password is required'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password')], 'Passwords must match')
.required('Confirm Password is required'),
});
const Form: React.FC = () => {
const initialValues: FormValues = {
name: '',
email: '',
password: '',
confirmPassword: '',
};
const handleSubmit = (values: FormikValues,
{ setSubmitting }: FormikHelpers<FormValues>) => {
setTimeout(() => {
console.log(values);
setSubmitting(false);
}, 500);
}
return (
<div className='main'>
<h1>Registration Form</h1>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ isSubmitting, errors, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<label htmlFor="name">
Name:
<Field name="name" type="text" className={errors.name ? 'error' : ''}/>
<ErrorMessage className='error' name="name" component="div" />
</label>
<label htmlFor="email">
Email:
<Field name="email" type="email" className={errors.email ? 'error' : ''}/>
<ErrorMessage className='error' name="email" component="div" />
</label>
<label htmlFor="password">
Password:
<Field name="password" type="password" className={errors.password ? 'error' : ''}/>
<ErrorMessage className='error' name="password" component="div" />
</label>
<label htmlFor="confirmPassword">
Confirm Password:
<Field name="confirmPassword" type="password" className={errors.confirmPassword ? 'error' : ''}/>
<ErrorMessage className='error' name="confirmPassword" component="div" />
</label>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
)}
</Formik>
</div>
);
};
export default Form;
If you prefer not to use the Formik component for form handling, you can still implement form validation with Yup in React without relying on wrapping the form with Formik as implemented above. Instead, you can directly use the Yup validation schema anduseFormik
hook from Formik to handle form submissions using React’s built-in form handling and state management capabilities.
Here’s an example code snippet demonstrating how to implement form validation with Yup in React and the useFormik hook:
import React, { useState } from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
interface FormValues {
name: string;
email: string;
password: string;
confirmPassword: string;
}
const Form: React.FC = () => {
const [submitting, setSubmitting] = useState(false);
const validationSchema = Yup.object().shape({
name: Yup.string()
.min(2, 'Name must be minimum 2')
.max(100, 'Name must not be more than 100 characters')
.required('Name is required'),
email: Yup.string().email('Invalid email').required('Email is required'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Password is required'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password')], 'Passwords must match')
.required('Confirm Password is required'),
});
const handleSubmit = async (values: FormValues) => {
try {
setSubmitting(true);
// Perform form submission logic here
console.log(values);
// Set submitting to false after successful submission
setSubmitting(false);
} catch (error) {
// Handle form submission error
console.error(error);
setSubmitting(false);
}
};
const formik = useFormik({
initialValues: {
name: '',
email: '',
password: '',
confirmPassword: '',
},
validationSchema,
onSubmit: handleSubmit,
});
return (
<div>
<h1>Registration Form</h1>
<form onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
name="name"
value={formik.values.name}
onChange={formik.handleChange}
/>
{formik.touched.name && formik.errors.name && (
<div className="error">{formik.errors.name}</div>
)}
</div>
<div>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
name="email"
value={formik.values.email}
onChange={formik.handleChange}
/>
{formik.touched.email && formik.errors.email && (
<div className="error">{formik.errors.email}</div>
)}
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
name="password"
value={formik.values.password}
onChange={formik.handleChange}
/>
{formik.touched.password && formik.errors.password && (
<div className="error">{formik.errors.password}</div>
)}
</div>
<div>
<label htmlFor="confirmPassword">Confirm Password</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
value={formik.values.confirmPassword}
onChange={formik.handleChange}
/>
{formik.touched.confirmPassword && formik.errors.confirmPassword && (
<div className="error">{formik.errors.confirmPassword}</div>
)}
</div>
<button type="submit" disabled={submitting}>
Submit
</button>
</form>
</div>
);
};
export default Form;
In this code example, we use the useFormik
hook from Formik to handle form state management and validation. We define the validation schema using Yup and handle form submission using the onSubmit
function. The form inputs are controlled using the value
and onChange
properties, and validation errors are displayed based on the formik touched
and errors
properties.
By using this approach, you have the flexibility to handle form validation and submission in a way that suits your requirements without relying on the Formik component.
In case you prefer a reusable custom hook that you can use across the entire codebase and in other future projects, see the link below.
Conclusion
In this comprehensive guide, we looked at how to use Formik and Yup to implement form validation in React while leveraging the power of TypeScript. We covered project setup, form component creation, defining validation schemas with Yup, integrating Formik and Yup, displaying validation errors, and implementing advanced validation techniques.
By combining the capabilities of Formik and Yup, you can create highly sophisticated and user-friendly forms with seamless validation experiences. Remember to customize the validation rules to your specific form requirements and to take advantage of advanced techniques such as conditional validation, custom validation functions, asynchronous validation, and disabling the submit button.
With the knowledge gained from this guide, you are well-equipped to handle form validation challenges in your React applications. Form validation in TypeScript with Formik and Yup not only improves your development workflow but also ensures your forms are robust, secure, and user-friendly.
You can read about using React Hook Form with Zod, another powerful library for form validation, in my article below.
Now, go ahead and create amazing forms with confidence!
The demo’s GitHub repository is available here, where you can view the entire code as well as the CSS stylings.
Happy coding!