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:
- First, you need to install Formik. You can do this using npm or yarn. For this guide, we'll use npm:
npm install formik
- 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.
Recommended Best Practices for Formik Validation
- 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.