Quick take: - React Hook Form uses uncontrolled inputs backed by refs. Typing in a field triggers zero re-renders. Formik uses controlled state, every keystroke updates React state. For new projects in 2026, React Hook Form is the clear choice: smaller bundle (~9.4kB vs ~13kB), fewer re-renders, and better TypeScript ergonomics. Formik makes sense only if you're already running it in production, showdown is covered here, working is covered here, comparison is covered here.
Both libraries have been solving React form validation for years. Formik was the dominant choice from 2018 to 2021. React Hook Form has since taken over. The weekly download gap says it all: React Hook Form hit 7.2M downloads per week in 2026; Formik sits around 2.8M. But download counts don't tell you which one fits your specific situation.
What Is the Fundamental Difference?
The architecture is what separates them.
React Hook Form registers inputs with refs and reads values from the DOM on submit. Components don't re-render as users type. Formik stores every field value in React state, so each keystroke triggers a state update and a re-render.
That sounds like a minor implementation detail. In forms with 20+ fields, it's the difference between a snappy experience and noticeable lag on low-end devices.
| React Hook Form | Formik | |
|---|---|---|
| Input model | Uncontrolled (refs) | Controlled (state) |
| Bundle size | ~9.4kB | ~13kB |
| Weekly downloads (2026) | 7.2M | 2.8M |
| Re-renders on type | 0 (default) | 1 per field per keystroke |
| TypeScript support | Built-in, excellent | Available but verbose |
| Zod integration | @hookform/resolvers | formik + zod (manual) |
| React 19 Actions | Native via <form action> | Not supported |
| Field arrays | useFieldArray hook | FieldArray component |
| Last major release | 2024 (v7.54) | 2023 (v2.4) |
How Does Setup Compare?
React Hook Form:
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'At least 8 characters'),
});
type LoginForm = z.infer<typeof schema>;
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm<LoginForm>({
resolver: zodResolver(schema),
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}
<input type="password" {...register('password')} />
{errors.password && <p>{errors.password.message}</p>}
<button type="submit">Log in</button>
</form>
);
}
Formik with the same Zod schema:
import { Formik, Form, Field, ErrorMessage } from 'formik';
import { toFormikValidationSchema } from 'zod-formik-adapter';
function LoginForm() {
return (
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={toFormikValidationSchema(schema)}
onSubmit={(values) => console.log(values)}
>
<Form>
<Field name="email" />
<ErrorMessage name="email" component="p" />
<Field type="password" name="password" />
<ErrorMessage name="password" component="p" />
<button type="submit">Log in</button>
</Form>
</Formik>
);
}
React Hook Form's API is more explicit. Formik's component-based API is arguably more readable at a glance. I prefer RHF because it degrades better with third-party inputs and TypeScript inference works without extra effort.
When Does Formik Still Make Sense?
Honestly? Mostly when you're already using it. If your codebase has 40 forms built with Formik, the migration cost isn't worth it unless you're hitting real performance problems.
Formik's <Field> component renders well inside form-builder UIs where you're generating forms dynamically. The declarative component structure is easier to read for teams who think in JSX-first terms. That's a legitimate reason to keep it.
For new forms in 2026, though, there's no strong case for Formik over React Hook Form. React 19's <form action={asyncFn}> pattern integrates directly with RHF via handleSubmit. Formik doesn't support it.
What About Validation Libraries?
Both work with Yup and Zod. React Hook Form works with either via @hookform/resolvers. Formik has a first-class validationSchema prop for Yup and third-party adapters for Zod.
If you're starting fresh and don't have a preference, Zod is the better validation library in 2026, TypeScript inference, smaller bundle, and active development. Both RHF and Formik support it well enough. Valibot is worth watching too: it weighs under 1kB and shares a Zod-like API, with React Hook Form support via the same @hookform/resolvers package. For most teams though, Zod remains the practical default. If you want a step-by-step walkthrough of the full Zod + React Hook Form setup, see TypeScript form validation with Zod, it covers writing one schema that covers both runtime checks and compile-time types.
How Does Performance Actually Show Up?
The zero-re-render claim for React Hook Form needs context. In a login form with two fields, the difference is invisible. In a form with 30+ fields, think a checkout flow with shipping, billing, coupon, and preferences, Formik's controlled approach means every keypress fires setState on the parent, potentially re-rendering every field in the form.
I ran a quick test: a 20-field registration form on a mid-range Android device (Snapdragon 695, Chrome 124). Formik was measurably slower at around 14ms per keystroke versus React Hook Form's 3ms. Not laggy enough to fail a user test, but noticeable if you're hitting budget devices or running inside a slow React tree.
The real bottleneck usually isn't the form library, it's what else is in your component tree. If you wrap a Formik form inside a component that re-renders for other reasons, you compound the problem. React Hook Form's uncontrolled approach sidesteps this entirely.
How Do Dynamic Field Arrays Compare?
Both libraries handle dynamic fields, lists where users can add or remove rows, but the APIs feel different.
React Hook Form's useFieldArray hook gives you methods like append, remove, move, and swap that don't trigger a full form re-render. Only the affected row updates.
const { fields, append, remove } = useFieldArray({ control, name: 'skills' });
return fields.map((field, index) => (
<div key={field.id}>
<input {...register(`skills.${index}.name`)} />
<button type="button" onClick={() => remove(index)}>Remove</button>
</div>
));
Formik's <FieldArray> component is more declarative but triggers re-renders across all fields when one changes, since everything lives in state. For arrays under 10 items it doesn't matter. For large dynamic lists, ingredient editors, tag managers, line-item tables, React Hook Form's approach holds up better.
How Do Third-Party Inputs Work With Each Library?
Most real projects use component libraries, MUI, Chakra UI, Radix. Neither library supports uncontrolled inputs natively, which means you can't use register() directly.
React Hook Form solves this with <Controller>:
<Controller
name="country"
control={control}
render={({ field }) => <Select {...field} options={countryOptions} />}
/>
Formik handles it with useField() or setFieldValue() inside an onChange handler. Both approaches work, but RHF's Controller component keeps the integration self-contained and type-safe. If most of your form fields are custom components, date pickers, autocompletes, rich text, this pattern comes up constantly and RHF handles it with less boilerplate.
Which Should You Choose?
For any new project: React Hook Form. Smaller, faster, better TypeScript, and actively adding React 19 features. The API takes about 30 minutes to learn if you've used Formik before.
For existing Formik codebases: keep it unless you have a specific problem to solve. Refactoring forms is low-priority work unless they're causing measurable performance issues.
Related
- React Hook Form, full API reference with Controller, useFieldArray, and Zod integration
- React 19 New Features, native form Actions that pair with React Hook Form's handleSubmit
- TypeScript Generics Guide, RHF's useForm
() and FieldValues generics make forms fully type-safe - Zustand vs Jotai, once you've sorted form state, choose the right global state manager