formik, react-final-form, react-hook-form, and redux-form are React form libraries that handle form state, submission, and validation. vee-validate is a Vue-focused validation library with some React support, while yup is a standalone JavaScript schema validation library often used alongside form libraries. Together, these tools address the complexities of managing user input, validation logic, and UI feedback in modern web applications.
Managing forms in React involves tracking user input, validating data, handling submission, and providing feedback — all while keeping performance and code maintainability in check. The libraries formik, react-final-form, react-hook-form, and redux-form tackle this problem differently. yup isn’t a form library but a validation schema tool often used alongside them, while vee-validate is primarily for Vue. Let’s break down how they work in practice.
redux-form Is Obsoleteredux-form is officially deprecated and should not be used in new projects. Its GitHub repository and npm page clearly state it’s unmaintained. It stores entire form state in Redux, causing excessive re-renders and store bloat. Modern alternatives avoid this by keeping form state localized.
// redux-form: Avoid in new code
import { reduxForm } from 'redux-form';
const MyForm = ({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="email" component="input" />
</form>
);
export default reduxForm({ form: 'myForm' })(MyForm);
Migrate existing redux-form implementations to react-hook-form or formik for better performance and active maintenance.
formik – Controlled Components with Contextformik uses React context to pass form state down, wrapping your form in a <Formik> provider. Fields are typically controlled, meaning every keystroke triggers a state update and potential re-render.
// formik
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
const schema = Yup.object({ email: Yup.string().email().required() });
<Formik
initialValues={{ email: '' }}
validationSchema={schema}
onSubmit={(values) => console.log(values)}
>
{({ errors, touched }) => (
<Form>
<Field name="email" />
{touched.email && errors.email && <div>{errors.email}</div>}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
This approach is intuitive but can cause performance issues in large forms due to full re-renders on every change.
react-final-form – Subscription-Based RenderingBuilt on final-form (a framework-agnostic core), react-final-form uses subscriptions to update only the parts of the UI that depend on specific fields. This minimizes re-renders.
// react-final-form
import { Form, Field } from 'react-final-form';
import { object, string } from 'yup';
const validate = async (values) => {
try {
await object({ email: string().email().required() }).validate(values, { abortEarly: false });
} catch (err) {
const errors = {};
err.inner.forEach(e => errors[e.path] = e.message);
return errors;
}
};
<Form
onSubmit={(values) => console.log(values)}
validate={validate}
render={({ handleSubmit, submitting }) => (
<form onSubmit={handleSubmit}>
<Field name="email">
{({ input, meta }) => (
<>
<input {...input} />
{meta.error && meta.touched && <span>{meta.error}</span>}
</>
)}
</Field>
<button type="submit" disabled={submitting}>Submit</button>
</form>
)}
/>
You pay for this efficiency with more verbose render prop syntax, though hooks (useField) simplify it slightly.
react-hook-form – Uncontrolled Components with Refsreact-hook-form embraces uncontrolled inputs, registering them via refs and reading values directly from the DOM during validation/submission. This avoids unnecessary state updates.
// react-hook-form
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as Yup from '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((data) => console.log(data))}>
<input {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}
<button type="submit">Submit</button>
</form>
);
}
This leads to fewer re-renders and better performance, especially in large forms. It also supports controlled components when needed via Controller.
None of these libraries include their own validation engine — they delegate to external validators or custom functions.
formik has first-class yup integration via validationSchema.react-final-form accepts any async/sync validation function.react-hook-form supports yup, zod, joi, and others via resolver plugins.yup itself is a schema builder:// yup standalone usage
import * as Yup from 'yup';
const schema = Yup.object({
email: Yup.string().email('Invalid email').required('Required')
});
schema.validate({ email: 'test@example.com' })
.then(valid => console.log('Valid!'))
.catch(err => console.log(err.errors));
vee-validate offers Vue-specific directives and composables but lacks equivalent React ergonomics:
// vee-validate in React (not recommended)
// Requires manual setup and doesn't leverage React idioms
import { useField } from 'vee-validate';
function Input({ name }) {
const { value, errorMessage } = useField(name);
return (
<>
<input value={value} onChange={/* ... */} />
{errorMessage && <span>{errorMessage}</span>}
</>
);
}
For React, stick with libraries designed for the ecosystem.
formik: Full re-renders on every keystroke unless you memoize field components. Use useField and React.memo to mitigate.react-final-form: Only subscribed fields re-render. Efficient but requires understanding of subscription granularity.react-hook-form: Minimal re-renders by default. Validation runs only on submit or blur unless configured otherwise.redux-form: Every keystroke updates Redux store → entire app may re-render. Avoid.formik: Easy to test with Jest; inspect Formik state via innerRef.react-final-form: Debug with debug prop to log state changes.react-hook-form: DevTools extension available; form state accessible via formState.formik: Rich plugin ecosystem (e.g., formik-mui for Material UI).react-hook-form: Official resolvers for all major validation libraries; excellent TypeScript support.react-final-form: Flexible but smaller community; relies on final-form plugins.| Scenario | Recommended Library |
|---|---|
| Rapid prototyping with Yup | formik |
| Large forms requiring max performance | react-hook-form |
| Need fine-grained render control | react-final-form |
| New Vue project | vee-validate |
| Schema-only validation | yup |
| Legacy Redux app (migrate!) | Not redux-form |
For most new React projects, react-hook-form is the strongest choice: it’s fast, lightweight, and integrates cleanly with modern validation libraries. formik remains a solid option if you value convention over configuration and don’t have performance-critical forms. Avoid redux-form entirely, and don’t force vee-validate into React — it’s not built for it. Use yup whenever you need expressive, reusable validation schemas, regardless of your form library.
Choose react-hook-form if you prioritize performance, minimal re-renders, and uncontrolled component patterns. It works best when you want to reduce boilerplate, avoid unnecessary state updates, and integrate easily with native HTML validation or external validators like Yup or Zod.
Choose formik if you prefer a higher-level, component-based API with built-in support for Yup validation schemas and need strong TypeScript integration. It’s well-suited for medium-complexity forms where developer experience and rapid iteration matter more than minimal re-renders.
Choose vee-validate only if you’re working in a Vue application; its React support is limited and not a primary focus. For React, prefer dedicated form libraries that offer better integration, performance, and community resources.
Choose react-final-form if you’re already using Final Form’s core logic and want fine-grained control over rendering with render props or hooks. It’s ideal when you need to decouple form state from UI components and optimize performance through subscription-based updates.
Do not choose redux-form for new projects — it is officially deprecated and no longer maintained. Existing projects should migrate to alternatives like react-hook-form or formik due to performance issues and Redux store bloat.
Choose yup when you need a robust, standalone schema validation library that can be paired with any form solution (like formik or react-hook-form). It’s excellent for defining complex validation rules in a declarative way but does not manage form state or UI on its own.
Get started | API | Form Builder | FAQs | Examples
npm install react-hook-form
import { useForm } from 'react-hook-form';
function App() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register('firstName')} />
<input {...register('lastName', { required: true })} />
{errors.lastName && <p>Last name is required.</p>}
<input {...register('age', { pattern: /\d+/ })} />
{errors.age && <p>Please enter number for age.</p>}
<input type="submit" />
</form>
);
}
We’re incredibly grateful to these kind and generous sponsors for their support!
Thank you to our previous sponsors for your generous support!
Thanks go to all our backers! [Become a backer].
Thanks go to these wonderful people! [Become a contributor].
Documentation website supported and backed by Vercel