I used Formik for years. It was the obvious choice from roughly 2018 to 2021. Then react-hook-form became the community standard and I switched. Here's why that happened and whether it was the right call.
Formik's Approach
Formik manages forms using controlled inputs. Every keystroke triggers a state update. The form state lives in React state:
import { Formik, Form, Field, ErrorMessage } from 'formik' <Formik initialValues={{ email: '', password: '' }} validate={values => { const errors = {} if (!values.email) errors.email = 'Required' return errors }} onSubmit={(values) => console.log(values)} > {({ isSubmitting }) => ( <Form> <Field type="email" name="email" /> <ErrorMessage name="email" component="div" /> <button type="submit" disabled={isSubmitting}>Submit</button> </Form> )} </Formik>
Formik's API is intuitive. Field, Form, ErrorMessage — the names match what they do. The render prop pattern was clean.
Formik's problem: every keystroke re-renders the entire form. On a complex form with 20 fields this causes noticeable performance issues.
react-hook-form's Approach
react-hook-form uses uncontrolled inputs by default. Instead of tracking every value in state, it reads values from the DOM when needed:
import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' const schema = z.object({ email: z.string().email('Invalid email'), password: z.string().min(6, 'Min 6 characters'), }) function LoginForm() { const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(schema) }) return ( <form onSubmit={handleSubmit(data => console.log(data))}> <input {...register('email')} /> {errors.email && <span>{errors.email.message}</span>} <button type="submit">Submit</button> </form> ) }
Why this is better:
- Uncontrolled inputs = no re-render on each keystroke = faster, especially for large forms
registerjust setsref,name,onChange,onBlur— minimal overhead- Built-in zod integration via
@hookform/resolvers - TypeScript inference from zod schema is excellent
The zod Integration
This is honestly what made me switch permanently. The combination of react-hook-form + zod is the best form validation experience I've had in any framework:
const schema = z.object({ email: z.string().email(), password: z.string().min(8), confirmPassword: z.string() }).refine(data => data.password === data.confirmPassword, { message: "Passwords don't match", path: ['confirmPassword'] }) type FormData = z.infer<typeof schema> // TypeScript type automatically generated const { register, handleSubmit, formState: { errors } } = useForm<FormData>({ resolver: zodResolver(schema) })
You define the schema once. TypeScript types are derived from it. Validation runs against it. Error messages come from it. No duplication, no type drift.
Formik also has zod integration via yup or directly, but it's not as clean and z.infer<typeof schema> doesn't type formState.errors as precisely.
shadcn/ui Integration
shadcn/ui built its Form components specifically for react-hook-form:
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/components/ui/form' <Form {...form}> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input {...field} type="email" /> </FormControl> <FormMessage /> {/* Automatically shows error */} </FormItem> )} /> </Form>
FormMessage reads errors from the form context automatically. No manual error display. This is genuinely excellent DX.
Bundle Size
react-hook-form: ~25KB minified Formik: ~45KB minified
Not a massive difference but react-hook-form wins.
When to Keep Using Formik
If you have a large existing Formik codebase that works, don't migrate for the sake of it. The migration is mechanical but not trivial for a large form library. Formik works fine.
For new projects: react-hook-form. The performance advantage, TypeScript support, zod integration and shadcn/ui compatibility make it the clear choice in 2026.
Frequently Asked Questions
Related Comparisons
Already Decided on Bootstrap?
Get a complete Angular 21 + Bootstrap 5 admin dashboard template — production ready.
Browse Templates →Use code FIRST30 for 30% off.