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.
React Hook Form and Formik are both React form libraries that handle field state, validation, and submission, but they take opposite approaches: React Hook Form reads values from uncontrolled DOM refs, while Formik stores every field in React state. 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.
Uncontrolled input is a form field whose value lives in the DOM itself rather than in React state, accessed via a ref only when needed, such as on submit. Controlled input is a form field whose value is stored in React state and updated on every change event, which is the model Formik uses for every field by default.
React Hook Form isn't standing still either. Version 8 hit beta in early 2026 with first-class support for React Compiler, meaning it needs zero extra config to play nicely with automatic memoization, plus simpler flat data structures for useFieldArray. A few APIs are shifting under the hood: the register function now hands back the input ref directly instead of a wrapped object, and the old watch subscription callback is being replaced by a dedicated subscribe method. If you're starting a new project today, it's worth reading the v7-to-v8 migration notes before you build against v7 patterns that are about to be deprecated. Formik hasn't shipped a comparable update; its last major release is still v2.4 from 2023, and open questions about who's actively maintaining it keep resurfacing in its GitHub issues.
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 (flat arrays in v8) | FieldArray component |
| React Compiler support | First-class, no config | Not addressed |
| Last major release | v8.0.0-beta (2026) | 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.
The gap shows up fastest on a long form on a mid-range Android phone. Formik re-renders the whole form on every keystroke, while React Hook Form isolates re-renders per field, and React Hook Form's own performance comparison shows an order-of-magnitude difference in re-render counts. Not enough to fail a user test on desktop, but noticeable on budget devices or 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.
Here's how to decide in practice:
- Check how many fields the form has. Under 10, either library performs fine and the choice comes down to team preference.
- Check whether you're starting a new project or maintaining an existing one. New projects should default to React Hook Form; existing Formik codebases rarely justify a rewrite.
- Check if you need React 19's native
<form action>Actions integration. Only React Hook Form supports it today. - Check your bundle budget. At roughly 9.4kB versus 13kB, React Hook Form saves real weight on forms-heavy pages.
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