final-form, formik, react-final-form, and redux-form are libraries designed to manage form state, validation, and submission in JavaScript applications—particularly React. final-form is a framework-agnostic form state engine, formik is a React-specific solution with a high-level component API, react-final-form provides React bindings for final-form, and redux-form integrates form state directly into a Redux store. These tools help developers avoid manual state management for inputs, errors, and submission logic, but differ significantly in architecture, performance, and ecosystem alignment.
Managing form state in React applications is a common but nuanced challenge. The libraries final-form, formik, react-final-form, and redux-form each offer different approaches to handling validation, submission, and field state. While they share the same goal — simplifying form logic — their architectures, integration patterns, and performance characteristics vary significantly. Let’s break down how they work in practice.
final-form is a framework-agnostic form state engine written in vanilla JavaScript. It doesn’t render anything or depend on React. Instead, it exposes a subscription-based API for tracking field values, errors, and meta states.
// final-form: pure JS engine
import { createForm } from 'final-form';
const form = createForm({
onSubmit: (values) => console.log(values),
validate: (values) => {
const errors = {};
if (!values.email) errors.email = 'Required';
return errors;
}
});
const unsubscribe = form.subscribe(console.log, { values: true });
form.registerField('email', () => {}, { value: true });
formik is a React-first library that uses React context and hooks under the hood. It manages form state internally and provides components like <Formik>, <Field>, and <ErrorMessage> to declaratively bind UI to logic.
// formik: React component model
import { Formik, Form, Field, ErrorMessage } from 'formik';
<Formik
initialValues={{ email: '' }}
validate={values => {
const errors = {};
if (!values.email) errors.email = 'Required';
return errors;
}}
onSubmit={(values) => console.log(values)}
>
<Form>
<Field name="email" />
<ErrorMessage name="email" />
<button type="submit">Submit</button>
</Form>
</Formik>
react-final-form is a React binding for final-form. It wraps the standalone engine with React components (<Form>, <Field>) and hooks (useField, useFormState), translating subscriptions into re-renders.
// react-final-form: bridge between final-form and React
import { Form, Field } from 'react-final-form';
<Form
onSubmit={console.log}
validate={values => {
const errors = {};
if (!values.email) errors.email = 'Required';
return errors;
}}
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="email">
{({ input, meta }) => (
<>
<input {...input} />
{meta.error && meta.touched && <span>{meta.error}</span>}
</>
)}
</Field>
<button type="submit">Submit</button>
</form>
)}
/>
redux-form tightly couples form state to a Redux store. Every field update dispatches an action, and form data lives in the global Redux tree under a form key.
// redux-form: Redux integration
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', // unique name
validate: values => {
const errors = {};
if (!values.email) errors.email = 'Required';
return errors;
}
})(MyForm);
final-form itself has zero re-renders because it’s not tied to any UI framework. Performance depends entirely on how you consume its state.
formik by default re-renders the entire form subtree on every keystroke unless you use useField or memoization. Large forms can become sluggish without optimization:
// formik: potential unnecessary re-renders
const MyInput = ({ name }) => {
const [field] = useField(name); // ✅ only re-renders when this field changes
return <input {...field} />;
};
react-final-form uses fine-grained subscriptions: each <Field> only re-renders when its own value, error, or meta changes. This makes it highly efficient for large or dynamic forms.
// react-final-form: automatic per-field rendering
<Field name="email">
{({ input, meta }) => <input {...input} />} {/* Only updates when email changes */}
</Field>
redux-form causes global Redux store updates on every field change. Since Redux triggers a full app re-render by default (unless you use reselect or connect optimizations), this can hurt performance in large apps.
💡 Note:
redux-formis officially deprecated. Its npm page states: "This library is no longer maintained. Please consider using Formik or React Final Form instead." New projects should avoid it.
All four support synchronous validation via a top-level validate function. But async and conditional validation differ.
final-form and react-final-form support field-level async validation through the validate prop on individual fields:
// react-final-form: async field validation
<Field name="username" validate={async value => {
const exists = await checkUsernameExists(value);
return exists ? 'Taken' : undefined;
}}>
{({ input, meta }) => (
<>
<input {...input} />
{meta.validating && <span>Checking...</span>}
{meta.error && <span>{meta.error}</span>}
</>
)}
</Field>
formik supports async validation at the form level via validate (which can return a Promise), but not per-field async validation out of the box. You’d need to manage loading states manually.
// formik: form-level async validation only
<Formik
validate={async values => {
const errors = {};
if (await usernameExists(values.username)) {
errors.username = 'Taken';
}
return errors;
}}
>
{/* ... */}
</Formik>
redux-form supports async validation via the asyncValidate prop, but it runs on blur or submit, not per keystroke, and requires careful configuration.
final-form: Zero dependencies. Can be used with Vue, Svelte, or vanilla JS.formik: Only depends on React. Works well with Yup for schema validation.react-final-form: Depends on final-form and React. Lightweight wrapper.redux-form: Requires Redux and adds significant boilerplate (reducers, store setup).If your app already uses Redux heavily and you need deep integration (e.g., saving partial form state to localStorage via middleware), redux-form might have made sense historically — but today, even Redux maintainers recommend against it.
formikreact-final-formfinal-formredux-form in new code. Migrate incrementally to react-final-form or formik.As confirmed by its npm page and GitHub repository, redux-form is deprecated and unmaintained. The README explicitly says: "Please consider using Formik or React Final Form instead." Do not use it in new projects.
| Package | Core Model | Re-render Strategy | Async Validation | Redux Required | Status |
|---|---|---|---|---|---|
final-form | Framework-agnostic | None (manual control) | ✅ (per-field) | ❌ | Active |
formik | React-only | Whole form (optimizable) | ❌ (form-level only) | ❌ | Active |
react-final-form | React + final-form | Per-field | ✅ (per-field) | ❌ | Active |
redux-form | Redux-bound | Global (via Redux) | ✅ (limited) | ✅ | Deprecated |
formik.react-final-form.final-form directly.redux-form — it’s outdated and unmaintained.Choose formik if you're building a React application and prioritize developer experience with minimal setup. It offers intuitive components like <Formik> and <Field>, integrates well with Yup for validation, and is well-suited for simple to moderately complex forms. Avoid it for very large forms unless you carefully optimize re-renders.
Choose final-form if you need a lightweight, framework-agnostic form state engine that works outside React (e.g., in Vue, Svelte, or vanilla JS). It gives you full control over rendering and subscriptions but requires more wiring to connect to UI components. Ideal for library authors or teams building cross-framework form solutions.
Choose react-final-form if you need high-performance form handling in React, especially for large, dynamic, or deeply nested forms. Its fine-grained re-rendering ensures only affected fields update, and it supports advanced features like field-level async validation. Best when you want the power of final-form with seamless React integration.
Do not choose redux-form for new projects—it is officially deprecated and unmaintained, as stated on its npm page and GitHub repository. While it was once popular for Redux-centric apps, modern alternatives like formik or react-final-form offer better performance, simpler APIs, and active support. Migrate existing usage when possible.
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):
Jared Palmer 💬 💻 🎨 📖 💡 🤔 👀 ⚠️ | Ian White 💬 🐛 💻 📖 🤔 👀 | Andrej Badin 💬 🐛 📖 | Adam Howard 💬 🐛 🤔 👀 | Vlad Shcherbin 💬 🐛 🤔 | Brikou CARRE 🐛 📖 | Sam Kvale 🐛 💻 ⚠️ |
|---|---|---|---|---|---|---|
Jon Tansey 🐛 💻 | Tyler Martinez 🐛 📖 | Tobias Lohse 🐛 💻 |
This project follows the all-contributors specification. Contributions of any kind welcome!