Formik for React Forms: Simplify State and Validation
Formik is a popular React library that eliminates form boilerplate by managing form state, submissions, and validation. The useFormik hook provides a minimal API to handle form data, display errors, and submit values, reducing hundreds of lines of manual state management code to just a few lines.
Key Takeaways
- Formik Eliminates Boilerplate: Manual form state management requires managing individual field states; Formik handles this automatically.
useFormikHook: The core of Formik, providingvalues,handleChange,handleSubmit, anderrorsin one object.- Built-in Validation: Formik includes a
validatefunction for synchronous validation that returns an errors object. - Error Display: The
errorsobject maps field names to error messages, making it easy to show validation feedback. - Works with Controlled Components: Formik manages controlled inputs using
valueandonChange, the standard React pattern.
Prerequisites
Before reading this article, ensure you understand:
- Controlled Components: How React manages form inputs via state and
onChangehandlers. - Form Submission: How HTML form submission works and how to handle it in React.
- npm/yarn: Basic package installation and import syntax.
Why Use Formik?
Building forms from scratch in React requires managing several things:
- Form State: Each field needs its own state variable (or one large state object).
- Change Handlers: Each field needs an
onChangehandler that updates state. - Validation: You must write validation logic and manage which fields have errors.
- Error Display: You must conditionally render error messages for each field.
- Submission: You must handle form submission and validation before submitting.
This leads to repetitive, error-prone boilerplate. Here's a minimal form without Formik:
import React, { useState } from 'react';
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState({});
const validate = (values) => {
const newErrors = {};
if (!values.email) {
newErrors.email = 'Email is required';
}
if (!values.password) {
newErrors.password = 'Password is required';
}
return newErrors;
};
const handleSubmit = (e) => {
e.preventDefault();
const newErrors = validate({ email, password });
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
} else {
console.log('Form submitted', { email, password });
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
{errors.email && <div>{errors.email}</div>}
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
name="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{errors.password && <div>{errors.password}</div>}
</div>
<button type="submit">Submit</button>
</form>
);
}
export default LoginForm;
This is just two fields. With Formik, the same form is much simpler.
Installing Formik
Add Formik to your project:
npm install formik
Or with yarn:
yarn add formik
Building a Formik Form with useFormik
The useFormik hook is the simplest way to integrate Formik. It takes a configuration object and returns a formik object with everything you need.
import React from 'react';
import { useFormik } from 'formik';
function LoginForm() {
const formik = useFormik({
initialValues: {
email: '',
password: '',
},
onSubmit: (values) => {
console.log('Form submitted:', values);
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
name="password"
type="password"
onChange={formik.handleChange}
value={formik.values.password}
/>
</div>
<button type="submit">Submit</button>
</form>
);
}
export default LoginForm;
Code Breakdown:
initialValues: An object with default values for each field. Formik initializes form state from this.onSubmit: A callback function called when the form is submitted (after validation passes).formik.handleChange: Auto-wired to updateformik.values[fieldName]when an input changes. The input'snameattribute is crucial—it must match the key ininitialValues.formik.values: The current form values. Useformik.values.emailto access the email field.formik.handleSubmit: Wraps your submission logic and handles validation before callingonSubmit.
That's significantly simpler than manual state management. Now let's add validation.
Adding Validation with Formik
Formik supports synchronous validation via the validate function. This function receives the form values and returns an errors object. If a field has no error, don't include it in the errors object.
import React from 'react';
import { useFormik } from 'formik';
function LoginForm() {
const formik = useFormik({
initialValues: {
email: '',
password: '',
},
validate: (values) => {
const errors = {};
// Email validation
if (!values.email) {
errors.email = 'Email is required';
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address';
}
// Password validation
if (!values.password) {
errors.password = 'Password is required';
} else if (values.password.length < 6) {
errors.password = 'Password must be at least 6 characters';
}
return errors;
},
onSubmit: (values) => {
console.log('Form submitted:', values);
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
{formik.errors.email && (
<div style={{ color: 'red' }}>{formik.errors.email}</div>
)}
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
name="password"
type="password"
onChange={formik.handleChange}
value={formik.values.password}
/>
{formik.errors.password && (
<div style={{ color: 'red' }}>{formik.errors.password}</div>
)}
</div>
<button type="submit">Submit</button>
</form>
);
}
export default LoginForm;
How Validation Works:
- When the form is submitted, Formik calls the
validatefunction with currentvalues. - If
validatereturns an empty object{}, there are no errors andonSubmitis called. - If
validatereturns errors like{ email: 'Invalid email address' },onSubmitis not called and the errors are available informik.errors. - Display errors by checking
formik.errors[fieldName]and rendering conditionally.
Advanced Validation with Yup (Preview)
Formik integrates seamlessly with Yup, a schema validation library. Instead of writing validation logic manually, you define a schema:
import * as Yup from 'yup';
import { useFormik } from 'formik';
const validationSchema = Yup.object({
email: Yup.string()
.email('Invalid email address')
.required('Email is required'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Password is required'),
});
function LoginForm() {
const formik = useFormik({
initialValues: { email: '', password: '' },
validationSchema,
onSubmit: (values) => {
console.log('Valid form:', values);
},
});
return (
// Same JSX as before
);
}
This approach scales better for complex forms because the schema is declarative and reusable.
Formik's Useful Properties
Once you've created a formik instance, it includes several helpful properties:
formik.values: Current field values.formik.errors: Validation errors (empty object if valid).formik.touched: Fields the user has interacted with (useful for showing errors only on touched fields).formik.isSubmitting:truewhile submission is in progress (useful for disabling the submit button).formik.isValid:trueif there are no validation errors.formik.dirty:trueif the form has been modified since initial load.
Frequently Asked Questions
Do I Have to Use the name Attribute in Inputs?
Yes. Formik uses the name attribute to map input changes to the values object. The name must exactly match the key in initialValues. If an input has no name, Formik cannot track it.
Can I Use Formik with Class Components?
No, useFormik is a hook and only works with function components. For class components, use the <Formik> render-prop component instead (covered in Part 2).
What's the Difference Between Formik's validate and Yup Validation?
validate is Formik's built-in synchronous validation function. Yup is a separate library that provides a declarative schema-based validation approach. Yup scales better for complex forms but adds a dependency. For simple forms, Formik's built-in validation is sufficient.
How Do I Handle Async Validation (e.g., Checking if Email Is Already Registered)?
Formik supports async validation, but it's more complex. You can return a Promise from the validate function or use Yup's .test() method for async rules. This is covered in advanced Formik tutorials.
Can I Submit a Form Programmatically?
Yes, call formik.submitForm() to trigger validation and submission without a submit button. This is useful for multi-step forms or conditional submissions.