In the dynamic world of React development, building and managing forms can quickly become a complex and tedious task. Handling state, validation, and submission logic for even simple forms can lead to a lot of boilerplate code, making your codebase cluttered and difficult to maintain. This is where Formik comes to the rescue. Formik is a small, yet powerful, library that simplifies form management in React. It abstracts away the complexities, allowing you to focus on building great user experiences.
What is Formik and Why Use It?
Formik is a lightweight library that helps you with the following tasks:
- Managing form state (values, errors, touched)
- Validating form data
- Handling form submission
It’s designed to be easy to use and integrates seamlessly with React. By using Formik, you can significantly reduce the amount of code you write for form handling, making your applications cleaner, more maintainable, and less prone to errors. Formik handles the repetitive tasks, allowing you to concentrate on the unique aspects of your forms and the overall user experience.
Setting Up Formik in Your React Project
Before diving into the code, you need to install Formik in your React project. You can do this using npm or yarn:
npm install formik --save
or
yarn add formik
After the installation is complete, you can start using Formik in your React components.
Basic Form Example
Let’s start with a simple example of a form with a single input field. This will demonstrate the core concepts of Formik. We will create a form that asks the user for their name.
Here’s the code:
import React from 'react';
import { useFormik } from 'formik';
const MyForm = () => {
const formik = useFormik({
initialValues: {
name: '' // Initial value for the 'name' field
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2)); // Display form values on submit
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="name">Name:</label>
<input
type="text"
id="name"
name="name"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.name}
/>
{/* Display errors if any */}
<button type="submit">Submit</button>
</form>
);
};
export default MyForm;
Let’s break down this example:
- Importing `useFormik`: We import the `useFormik` hook from the `formik` library. This is the core of Formik.
- `useFormik` Hook: We call the `useFormik` hook, passing it an object with configuration options.
- `initialValues`: This object sets the initial values for your form fields. Here, we initialize the `name` field to an empty string.
- `onSubmit`: This function is called when the form is submitted. It receives the form values as an argument. In this example, we’re simply displaying the form values using `alert`. In a real application, you would use this to send data to your server.
- Form Elements: We create a standard HTML form with an input field for the name.
- `onChange`, `onBlur`, `value`: We bind the input field’s `onChange`, `onBlur`, and `value` props to Formik’s methods and state.
- `formik.handleSubmit`: The form’s `onSubmit` event is bound to `formik.handleSubmit`, which handles the form submission logic.
Adding Validation with Yup
One of the most powerful features of Formik is its ability to easily handle form validation. Formik doesn’t come with a built-in validation library, but it integrates seamlessly with other validation libraries like Yup. Yup is a JavaScript schema builder for value parsing and validation. It allows you to define the shape and validation rules for your form data.
First, install Yup:
npm install yup --save
or
yarn add yup
Now, let’s add validation to our form using Yup. We’ll ensure that the name field is required.
import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
const MyForm = () => {
const formik = useFormik({
initialValues: {
name: ''
},
validationSchema: Yup.object({
name: Yup.string()
.required('Required')
}),
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="name">Name:</label>
<input
type="text"
id="name"
name="name"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.name}
/>
{formik.touched.name && formik.errors.name ? (
<div className="error">{formik.errors.name}</div>
) : null}
<button type="submit">Submit</button>
</form>
);
};
export default MyForm;
Key changes in this example:
- Import Yup: We import Yup at the top of the file.
- `validationSchema`: We add a `validationSchema` property to the `useFormik` configuration. This property takes a Yup schema.
- Yup Schema: Inside the `validationSchema`, we define the validation rules for each field. In this case, we use `Yup.string().required(‘Required’)` to specify that the `name` field must be a string and is required. If the field is left empty, an error message “Required” will be displayed.
- Error Display: We use `formik.touched` and `formik.errors` to display validation errors. `formik.touched.name` checks if the field has been touched (blurred), and `formik.errors.name` contains the error message if there’s a validation error.
Handling Multiple Fields and Complex Forms
Formik shines when dealing with more complex forms. Let’s create a form with multiple fields, including an email and a password field, and add more comprehensive validation.
import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
const RegistrationForm = () => {
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: '',
},
validationSchema: Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string()
.email('Invalid email address')
.required('Required'),
password: Yup.string()
.min(8, 'Must be at least 8 characters')
.required('Required'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password'), null], 'Passwords must match')
.required('Required'),
}),
onSubmit: (values, { resetForm }) => {
alert(JSON.stringify(values, null, 2));
resetForm(); // Resets the form after submission
},
});
return (
<form onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="firstName">First Name</label>
<input
type="text"
id="firstName"
name="firstName"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.firstName}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div className="error">{formik.errors.firstName}</div>
) : null}
</div>
<div>
<label htmlFor="lastName">Last Name</label>
<input
type="text"
id="lastName"
name="lastName"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.lastName}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div className="error">{formik.errors.lastName}</div>
) : null}
</div>
<div>
<label htmlFor="email">Email Address</label>
<input
type="email"
id="email"
name="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email ? (
<div className="error">{formik.errors.email}</div>
) : null}
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
name="password"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.password}
/>
{formik.touched.password && formik.errors.password ? (
<div className="error">{formik.errors.password}</div>
) : null}
</div>
<div>
<label htmlFor="confirmPassword">Confirm Password</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.confirmPassword}
/>
{formik.touched.confirmPassword && formik.errors.confirmPassword ? (
<div className="error">{formik.errors.confirmPassword}</div>
) : null}
</div>
<button type="submit" disabled={!formik.isValid}>Submit</button>
</form>
);
};
export default RegistrationForm;
Key improvements in this example:
- Multiple Fields: The form now includes `firstName`, `lastName`, `email`, `password`, and `confirmPassword` fields.
- More Complex Validation: The Yup schema now includes more complex validation rules, such as maximum length for `firstName` and `lastName`, email format validation for `email`, minimum length for `password`, and a password confirmation check.
- `oneOf` Validation: The `confirmPassword` field uses `Yup.string().oneOf([Yup.ref(‘password’), null], ‘Passwords must match’)` to ensure that the confirmation password matches the password field. `Yup.ref(‘password’)` references the value of the password field.
- Form Reset: The `onSubmit` function now includes `resetForm()`. This clears the form values after successful submission.
- `isValid` Prop: The submit button is disabled unless the form is valid using the `formik.isValid` prop.
Advanced Formik Techniques
Formik offers several advanced features that can help you build more sophisticated forms. Let’s look at a few of them.
1. Using Field Arrays
Field arrays are useful when you need to handle dynamic lists of inputs, such as adding and removing items from a list. Formik, in combination with libraries like `formik-array`, makes this easy.
First, install `formik-array`:
npm install formik-array --save
or
yarn add formik-array
Here’s an example of how to use field arrays to manage a list of hobbies:
import React from 'react';
import { useFormik, FieldArray } from 'formik';
import * as Yup from 'yup';
const HobbiesForm = () => {
const formik = useFormik({
initialValues: {
hobbies: ['coding', 'reading'], // Initial hobbies
},
validationSchema: Yup.object({
hobbies: Yup.array().of(Yup.string().required('Hobby is required')),
}),
onSubmit: (values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
},
});
return (
<form onSubmit={formik.handleSubmit}>
<FieldArray name="hobbies">
{
({ insert, remove, fields }) => (
<div>
{fields.map((name, index) => (
<div key={index} className="hobby-item">
<label htmlFor={`hobbies.${index}`} >Hobby #{index + 1}</label>
<input
type="text"
name={`hobbies.${index}`}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.hobbies[index]}
/>
{formik.touched.hobbies && formik.errors.hobbies && formik.errors.hobbies[index] ? (
<div className="error">{formik.errors.hobbies[index]}</div>
) : null}
<button type="button" onClick={() => remove(index)}>Remove</button>
</div>
))}
<button
type="button"
onClick={() => insert(formik.values.hobbies.length, '')}
>
Add Hobby
</button>
</div>
)
}
</FieldArray>
<button type="submit" disabled={!formik.isValid || formik.isSubmitting}>
Submit
</button>
</form>
);
};
export default HobbiesForm;
Explanation:
- Import `FieldArray`: Import the `FieldArray` component from `formik`.
- Initial Values: Set the initial values for the `hobbies` array.
- Yup Validation: The `validationSchema` uses `Yup.array().of(Yup.string().required(‘Hobby is required’))` to validate each item in the hobbies array.
- `FieldArray` Component: Wrap the section that renders the array items with the `FieldArray` component. The `name` prop specifies the field array name in the form values.
- Render Fields: Inside the `FieldArray`, use the render prop to access methods to manipulate the array: `insert` (add an item), `remove` (remove an item), and `fields` (the array of field names).
- Mapping Items: Map over the `fields` array to render each hobby input. Use the index to create unique keys and field names.
- Add and Remove Buttons: Include buttons to add and remove hobby items.
2. Using Formik’s `useField` Hook
The `useField` hook is a convenient way to manage individual form fields within a component. It simplifies the process of connecting a field to Formik’s state.
import React from 'react';
import { useFormik, useField } from 'formik';
import * as Yup from 'yup';
const MyInput = ({ label, ...props }) => {
const [field, meta] = useField(props);
return (
<div>
<label htmlFor={props.id || props.name}>{label}</label>
<input {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
);
};
const CustomInputForm = () => {
const formik = useFormik({
initialValues: {
name: '',
},
validationSchema: Yup.object({
name: Yup.string().required('Name is required'),
}),
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<MyInput label="Name" type="text" id="name" name="name" />
<button type="submit">Submit</button>
</form>
);
};
export default CustomInputForm;
Explanation:
- `useField` Hook: We create a custom input component `MyInput` that uses the `useField` hook.
- Destructuring: The `useField` hook returns an array with two elements: `field` (an object containing the input’s props) and `meta` (an object containing meta information like `touched`, `error`, etc.).
- Props: The `MyInput` component accepts a `label` prop and spreads the rest of the props to the input element.
- Rendering: The input element receives the props from the `field` object returned by `useField`.
- Error Handling: The component displays errors using `meta.error`.
3. Using Formik’s `validate` Prop
The `validate` prop lets you perform custom validation outside of the Yup schema. This is useful for more complex validation logic that can’t be easily expressed in a schema. The `validate` prop is a function that receives the form values and returns an object of errors, keyed by the field name.
import React from 'react';
import { useFormik } from 'formik';
const CustomValidationForm = () => {
const formik = useFormik({
initialValues: {
email: '',
password: '',
},
validate: values => {
const errors = {};
if (!values.email) {
errors.email = 'Required';
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$/i.test(values.email)) {
errors.email = 'Invalid email address';
}
if (values.password && values.password.length < 8) {
errors.password = 'Must be at least 8 characters';
}
return errors;
},
onSubmit: (values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
},
});
return (
<form onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="email">Email Address</label>
<input
type="email"
id="email"
name="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email ? (
<div className="error">{formik.errors.email}</div>
) : null}
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
name="password"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.password}
/>
{formik.touched.password && formik.errors.password ? (
<div className="error">{formik.errors.password}</div>
) : null}
</div>
<button type="submit" disabled={formik.isSubmitting}>
Submit
</button>
</form>
);
};
export default CustomValidationForm;
Explanation:
- `validate` Prop: The `validate` prop is added to the `useFormik` configuration.
- Validation Function: The `validate` prop takes a function that receives the form values as an argument.
- Error Object: The validation function returns an object containing validation errors, keyed by the field name.
- Custom Validation Logic: Inside the validation function, we implement custom validation rules for the `email` field.
Common Mistakes and How to Fix Them
When working with Formik, you might encounter a few common pitfalls. Here are some of them and how to avoid them.
1. Not Using `onBlur` for Validation
A common mistake is forgetting to call `formik.handleBlur` on your input fields. Without `onBlur`, validation errors won’t be triggered when the user leaves a field, and the user won’t get immediate feedback.
Fix: Make sure to include `onBlur={formik.handleBlur}` on all your input fields. This triggers validation when the user moves focus away from the input.
2. Incorrectly Displaying Errors
Another common issue is displaying validation errors incorrectly. You need to check both `formik.touched` and `formik.errors` to display an error message only after the field has been blurred (touched) and has an error.
Fix: Use the following pattern to display errors:
{formik.touched.fieldName && formik.errors.fieldName ? (
<div className="error">{formik.errors.fieldName}</div>
) : null}
3. Not Handling Form Reset
After a successful form submission, it’s good practice to reset the form. This clears the form values and any validation errors. Without resetting, the user might be confused if the form values remain after submission.
Fix: Use the `resetForm()` function provided by Formik inside the `onSubmit` handler. For example: `onSubmit: (values, { resetForm }) => { … resetForm(); }`.
4. Forgetting to Disable the Submit Button
If your form has validation, it’s essential to disable the submit button while the form is invalid. This prevents the user from submitting the form with invalid data.
Fix: Use the `formik.isValid` prop to disable the submit button. For example: `<button type=”submit” disabled={!formik.isValid}>Submit</button>`.
Key Takeaways
- Formik simplifies form management in React by handling state, validation, and submission.
- Formik integrates well with Yup for schema-based validation.
- Use `useFormik` hook to manage form state and submission.
- Use `onBlur` to trigger validation on blur events.
- Display errors conditionally using `formik.touched` and `formik.errors`.
- Consider using field arrays for dynamic lists of inputs.
- Use the `useField` hook for creating custom input components.
- Use the `validate` prop for custom validation logic.
FAQ
1. How do I clear the form after submission?
Use the `resetForm()` function provided in the `onSubmit` handler. For example: `onSubmit: (values, { resetForm }) => { … resetForm(); }`.
2. How can I validate a field based on the value of another field?
You can use Yup’s `when` method or custom validation functions within the `validate` prop to implement conditional validation. The `when` method allows you to define validation rules that depend on the value of another field. The `validate` prop gives you the full form values to perform validation logic.
3. How do I handle file uploads with Formik?
Formik itself doesn’t directly handle file uploads. You’ll need to use the `onChange` event of the file input to update the form state with the selected file. You can then use a library or a custom function to upload the file to your server. Remember to handle the file input’s `onChange` event and store the file in your form state.
4. Can I use Formik with TypeScript?
Yes, Formik has excellent TypeScript support. You can define types for your form values and Yup schemas to ensure type safety throughout your form components.
Final Thoughts
Formik is a valuable tool for any React developer looking to streamline form management. By abstracting away the complexities of form handling, Formik allows you to focus on building user-friendly and efficient forms. From basic forms to complex multi-step processes, Formik offers the flexibility and power you need. As you explore its features, you’ll find that it not only simplifies your code but also makes it easier to maintain and extend your forms over time. Mastering Formik will undoubtedly enhance your React development skills and contribute to creating more robust and user-centric applications. Embrace Formik and experience a smoother and more efficient form-building process in your React projects, allowing you to focus on the core functionality that truly matters: creating delightful user experiences.
