Skip to main content

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.
  • useFormik Hook: The core of Formik, providing values, handleChange, handleSubmit, and errors in one object.
  • Built-in Validation: Formik includes a validate function for synchronous validation that returns an errors object.
  • Error Display: The errors object maps field names to error messages, making it easy to show validation feedback.
  • Works with Controlled Components: Formik manages controlled inputs using value and onChange, the standard React pattern.

Prerequisites

Before reading this article, ensure you understand:

  • Controlled Components: How React manages form inputs via state and onChange handlers.
  • 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:

  1. Form State: Each field needs its own state variable (or one large state object).
  2. Change Handlers: Each field needs an onChange handler that updates state.
  3. Validation: You must write validation logic and manage which fields have errors.
  4. Error Display: You must conditionally render error messages for each field.
  5. 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 update formik.values[fieldName] when an input changes. The input's name attribute is crucial—it must match the key in initialValues.
  • formik.values: The current form values. Use formik.values.email to access the email field.
  • formik.handleSubmit: Wraps your submission logic and handles validation before calling onSubmit.

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 validate function with current values.
  • If validate returns an empty object {}, there are no errors and onSubmit is called.
  • If validate returns errors like { email: 'Invalid email address' }, onSubmit is not called and the errors are available in formik.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: true while submission is in progress (useful for disabling the submit button).
  • formik.isValid: true if there are no validation errors.
  • formik.dirty: true if 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.

Further Reading