formik, react-hook-form, and redux-form are libraries designed to simplify form handling in React applications by managing state, validation, submission, and error messaging. formik uses React context to manage form state with a declarative API. react-hook-form leverages uncontrolled components and refs to minimize re-renders and maximize performance. redux-form integrates form state directly into the Redux store but is now deprecated and should not be used in new projects.
Managing forms in React apps involves handling state, validation, submission, and user feedback. formik, react-hook-form, and redux-form are three major libraries that aim to simplify this process β but they take very different approaches under the hood. Letβs break down how each works, where they shine, and what trade-offs youβll face.
formik keeps form state inside a React context provider (<Formik>). Every field update triggers re-renders of the entire form subtree unless you memoize components carefully.
// formik: Full form re-render on any change
import { Formik, Field, Form } from 'formik';
<Formik
initialValues={{ email: '' }}
onSubmit={(values) => console.log(values)}
>
<Form>
<Field name="email" />
<button type="submit">Submit</button>
</Form>
</Formik>
react-hook-form avoids unnecessary re-renders by storing form state outside Reactβs component tree (in a ref). Only the fields you explicitly subscribe to will re-render when their value changes.
// react-hook-form: Minimal re-renders
import { useForm } from 'react-hook-form';
function MyForm() {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(console.log)}>
<input {...register('email')} />
<button type="submit">Submit</button>
</form>
);
}
redux-form stores all form state in the global Redux store. Every keystroke dispatches an action, which can cause performance issues in large apps if not optimized with selectors or memoization.
// redux-form: Global Redux state updates
import { reduxForm, Field } from 'redux-form';
const MyForm = ({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="email" component="input" />
<button type="submit">Submit</button>
</form>
);
export default reduxForm({ form: 'myForm' })(MyForm);
β οΈ Important: As of 2024,
redux-formis officially deprecated. The npm page states: "This library is no longer maintained. We recommend using Formik or React Hook Form instead." Do not use it in new projects.
All three support synchronous and asynchronous validation, but their integration styles differ.
formik accepts a validate function or integrates with schema validators like Yup:
// formik + Yup
import * as Yup from 'yup';
<Formik
initialValues={{ email: '' }}
validationSchema={Yup.object({
email: Yup.string().email().required()
})}
onSubmit={console.log}
>
{/* ... */}
</Formik>
react-hook-form supports both manual validation rules and resolver-based integration with libraries like Yup, Zod, or Joi:
// react-hook-form + Yup
import { yupResolver } from '@hookform/resolvers/yup';
const schema = Yup.object({ email: Yup.string().email().required() });
function MyForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: yupResolver(schema)
});
return (
<form onSubmit={handleSubmit(console.log)}>
<input {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}
</form>
);
}
redux-form uses a validate prop that runs on every change:
// redux-form validation
const validate = values => {
const errors = {};
if (!values.email) errors.email = 'Required';
else if (!/^[^@]+@[^@]+$/.test(values.email)) errors.email = 'Invalid';
return errors;
};
export default reduxForm({ form: 'myForm', validate })(MyForm);
Adding/removing fields dynamically is common in complex forms (e.g., address lists, product variants).
formik provides useFieldArray for managing lists:
// formik: Field arrays
import { Formik, Field, FieldArray } from 'formik';
<Formik initialValues={{ emails: [''] }}>
{({ values }) => (
<FieldArray name="emails">
{({ push, remove }) => (
<div>
{values.emails.map((_, index) => (
<Field name={`emails[${index}]`} key={index} />
))}
<button type="button" onClick={() => push('')}>Add</button>
</div>
)}
</FieldArray>
)}
</Formik>
react-hook-form offers useFieldArray with similar capabilities but better performance due to its uncontrolled nature:
// react-hook-form: Field arrays
import { useForm, useFieldArray } from 'react-hook-form';
function MyForm() {
const { control, register } = useForm({
defaultValues: { emails: [''] }
});
const { fields, append, remove } = useFieldArray({
control,
name: 'emails'
});
return (
<form>
{fields.map((field, index) => (
<input key={field.id} {...register(`emails.${index}`)} />
))}
<button type="button" onClick={() => append('')}>Add</button>
</form>
);
}
redux-form requires manual management of array fields using action creators like array.push, which is more verbose and tightly coupled to Redux:
// redux-form: Array fields (deprecated approach)
import { arrayPush, arrayRemove } from 'redux-form';
// In mapDispatchToProps:
// addEmail: () => arrayPush('myForm', 'emails', ''),
// removeEmail: (index) => arrayRemove('myForm', 'emails', index)
All three work with popular component libraries (Material UI, Ant Design, etc.), but the ergonomics vary.
formik often requires wrapping third-party inputs in custom components to connect them to Formikβs context.react-hook-formβs Controller component makes it easy to adapt controlled components:// react-hook-form + Material UI
import { Controller } from 'react-hook-form';
import TextField from '@mui/material/TextField';
<Controller
name="email"
control={control}
render={({ field }) => <TextField {...field} />}
/>
redux-form used the component prop on <Field>, but again β itβs deprecated.formik: Form state is inspectable via React DevTools (since it lives in context), but excessive re-renders can make debugging noisy.react-hook-form: Provides a formState object with useful flags (isDirty, isValid, errors) and minimal re-renders make tests faster and more predictable.redux-form: Form state appears in Redux DevTools, which can be helpful for tracing actions β but at the cost of polluting the global store with transient UI state.| Feature | formik | react-hook-form | redux-form |
|---|---|---|---|
| State Location | React Context | Ref (outside React tree) | Redux Store |
| Re-renders | High (entire form) | Low (per-field) | High (global store updates) |
| Performance | Moderate | Excellent | Poor (deprecated) |
| Validation | Built-in + Yup | Resolvers (Yup, Zod, etc.) | Manual function |
| Dynamic Fields | useFieldArray | useFieldArray | Manual Redux actions |
| Learning Curve | Gentle | Moderate | Steep (Redux knowledge needed) |
| Maintenance Status | Actively maintained | Actively maintained | β Deprecated |
redux-form entirely β itβs deprecated and introduces unnecessary coupling to Redux for form state.formik if your team prefers a declarative, component-based API and doesnβt mind occasional performance tuning via React.memo.react-hook-form if you prioritize performance, want minimal bundle impact, and are comfortable with a more imperative registration pattern.In most new React projects today, react-hook-form is the strongest choice β itβs fast, flexible, and aligns well with modern React patterns like hooks and uncontrolled components.
Choose formik if your team prefers a component-driven, declarative API that feels natural in React and you're already comfortable managing potential performance impacts through memoization. Itβs well-suited for medium-complexity forms where developer experience and readability outweigh micro-optimizations.
Choose react-hook-form if you need maximum performance, minimal re-renders, and tight integration with modern validation libraries like Zod or Yup. Itβs ideal for large forms, dynamic field arrays, or performance-sensitive applications where every millisecond counts.
Do not choose redux-form for new projects β it is officially deprecated as stated on its npm page. If you maintain a legacy codebase still using it, plan a migration to either formik or react-hook-form.
Visit https://formik.org to get started with Formik.
List of organizations and projects using Formik
This monorepo uses yarn, so to start you'll need the package manager installed.
To run E2E tests you'll also need Playwright set up, which can be done locally via npx playwright install. Afterward, run yarn start:app and in a separate tab run yarn e2e:ui to boot up the test runner.
When you're done with your changes, we use changesets to manage release notes. Run yarn changeset to autogenerate notes to be appended to your pull request.
Thank you!
Formik is made with <3 thanks to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!