formik, react-final-form, and redux-form are three distinct approaches to handling form state in React applications. formik focuses on simplicity and reducing boilerplate by managing state locally within components using React hooks. react-final-form offers a high-performance, framework-agnostic core with a render-prop based React integration, emphasizing subscription-based updates to prevent unnecessary re-renders. redux-form stores all form state in a global Redux store, treating form inputs as part of the application's single source of truth, though it is now considered legacy for modern React architectures.
Managing form state in React often becomes a bottleneck for performance and code maintainability. While formik, react-final-form, and redux-form all solve the same problem, they use fundamentally different architectural patterns. Understanding these differences is crucial for avoiding performance pitfalls and unnecessary boilerplate.
The core difference lies in where the state lives and how updates propagate.
formik keeps form state local to the component tree using React state (via useState or useReducer). It triggers a re-render of the entire form component whenever any field changes unless you manually optimize it.
// formik: Local state management
import { useFormik } from 'formik';
function LoginForm() {
const formik = useFormik({
initialValues: { email: '', password: '' },
onSubmit: values => console.log(values),
});
// Changing 'email' re-renders the whole component
return (
<form onSubmit={formik.handleSubmit}>
<input
name="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
<button type="submit">Submit</button>
</form>
);
}
react-final-form uses a subscription model. The form state lives in a central context, but components only re-render when the specific field they subscribe to changes. This prevents the "whole form re-renders on every keystroke" issue by default.
// react-final-form: Subscription-based updates
import { Form, Field } from 'react-final-form';
function LoginForm() {
return (
<Form
onSubmit={async (values) => console.log(values)}
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="email">
{({ input, meta }) => (
<input {...input} placeholder="Email" />
)}
</Field>
{/* This button won't re-render when 'email' changes */}
<button type="submit">Submit</button>
</form>
)}
/>
);
}
redux-form forces all form state into the global Redux store. Every keystroke dispatches a Redux action, updates the global store, and triggers a connected component update. This creates significant overhead and boilerplate.
// redux-form: Global Redux state (Deprecated Pattern)
import { reduxForm, Field } from 'redux-form';
let LoginForm = ({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="email" component="input" placeholder="Email" />
<button type="submit">Submit</button>
</form>
);
// Must wrap with higher-order component and connect to Redux
LoginForm = reduxForm({ form: 'login' })(LoginForm);
Performance is often the deciding factor for large forms.
formik re-renders the entire form component on every field change by default. For simple forms, this is fine. For forms with 50+ fields, you must manually wrap fields in React.memo or use the <Field> component (available in newer versions) to optimize.
// formik: Manual optimization required for large forms
const MyInput = React.memo(({ name, formik }) => (
<input
name={name}
onChange={formik.handleChange}
value={formik.values[name]}
/>
));
// Usage
<MyInput name="email" formik={formik} />
react-final-form handles this automatically. Because <Field> subscribes only to its specific value, typing in one input does not trigger re-renders for other inputs or the submit button.
// react-final-form: Optimized by default
<Field name="complexCalculation">
{({ input }) => (
<ExpensiveComponent value={input.value} />
)}
</Field>
// ExpensiveComponent only re-renders if 'complexCalculation' changes
redux-form suffers from performance issues due to the Redux dispatch cycle. Every character typed triggers an action dispatch, store update, and provider notification. While selectors can help, the overhead is inherently higher than local state or fine-grained subscriptions.
All three support synchronous and asynchronous validation, but the implementation differs.
formik encourages a single validation function that returns an error object. It runs on change or blur based on configuration.
// formik: Object-based validation
const validate = (values) => {
let errors = {};
if (!values.email) {
errors.email = 'Required';
}
return errors;
};
const formik = useFormik({
initialValues: { email: '' },
validate,
onSubmit: values => {},
});
react-final-form uses a similar function signature but integrates tightly with the subscription model, allowing validation to run without triggering UI updates for unrelated fields.
// react-final-form: Validator function
const validate = (values) => {
const errors = {};
if (!values.email) {
errors.email = 'Required';
}
return errors;
};
<Form validate={validate} onSubmit={...} />
redux-form requires validation functions to be passed into the configuration object, often leading to complex setups when trying to share validators across different forms.
// redux-form: Config-based validation
const validate = (values) => {
const errors = {};
if (!values.email) errors.email = 'Required';
return errors;
};
export default reduxForm({
form: 'login',
validate
})(LoginForm);
formik offers the lowest barrier to entry. It uses hooks, feels like standard React, and requires very little setup. You don't need to wrap your app in a provider.
react-final-form has a steeper learning curve due to its render-prop pattern ({({ input, meta }) => ...}). However, it provides immense flexibility for custom input components. It requires wrapping the app in a <FormProvider> if using context features, though often not strictly required for basic usage.
redux-form has the highest boilerplate cost. You must set up Redux, configure the reducer, wrap components with higher-order components (HOCs), and manage action types. This pattern clashes with modern React hooks practices.
It is critical to note the maintenance status of these libraries:
redux-form: Deprecated. The repository is archived, and the maintainer explicitly advises against using it for new projects. The pattern of storing form state in Redux is no longer considered a best practice.formik: Actively maintained. Widely adopted in the community.react-final-form: Actively maintained. Known for stability and performance in enterprise settings.| Feature | formik | react-final-form | redux-form |
|---|---|---|---|
| State Location | Local Component State | Context + Subscription | Global Redux Store |
| Re-renders | Whole form (default) | Per-field (optimized) | Connected components |
| API Style | Hooks (useFormik) | Render Props (<Field>) | HOCs (reduxForm()) |
| Boilerplate | Low | Medium | High |
| Status | ā Active | ā Active | ā Deprecated |
For 90% of use cases, choose formik. It strikes the best balance between ease of use, community support, and modern React patterns. It is perfect for login forms, settings pages, and standard data entry.
Choose react-final-form if you are building high-performance data grids, complex financial applications, or need to share logic across React and React Native. Its subscription model solves rendering bottlenecks that formik might encounter in extreme scenarios.
Avoid redux-form entirely. It belongs to a previous era of React development. Migrating away from it reduces bundle size, improves performance, and simplifies your codebase.
Choose formik for most new projects where developer speed and simplicity are priorities. It is ideal for standard CRUD forms, wizards, and scenarios where you want minimal boilerplate without sacrificing type safety or validation capabilities. Its hook-based API fits naturally into modern functional components.
Choose react-final-form if you are building complex, high-frequency input interfaces (like trading dashboards or real-time data entry) where rendering performance is critical. It is also the right choice if you need to share validation logic between React, React Native, or vanilla JavaScript environments using the same core engine.
Do NOT choose redux-form for new projects. It is officially deprecated and unmaintained. Its pattern of storing transient form state in a global Redux store causes excessive re-renders and boilerplate in modern React versions. Migrate existing implementations to formik or react-final-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!