react-hook-form vs formik vs final-form
React Form State Management Libraries
react-hook-formformikfinal-formSimilar Packages:
React Form State Management Libraries

final-form, formik, and react-hook-form are popular libraries for managing form state, validation, and submission in React applications. They abstract away the boilerplate of handling user input, displaying errors, and synchronizing data across form fields. Each takes a different architectural approach: final-form uses a subscription-based model with state outside React, formik relies on React component state with controlled inputs, and react-hook-form leverages uncontrolled components with refs for optimal performance.

Npm Package Weekly Downloads Trend
3 Years
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
react-hook-form15,847,55944,3911.25 MB112a day agoMIT
formik3,246,49034,372585 kB8352 months agoApache-2.0
final-form468,6753,039382 kB1207 months agoMIT

Form State Management in React: final-form vs formik vs react-hook-form

Managing form state in React apps is a common but surprisingly tricky problem. While you could roll your own with useState, real-world forms need validation, error handling, performance optimization, and often complex nested structures. The three libraries — final-form, formik, and react-hook-form — each solve this problem differently. Let’s compare how they actually work in practice.

🧠 Core Philosophy: Where State Lives and How It Updates

final-form keeps form state outside React, in a standalone JavaScript object. It uses subscriptions to notify components only when their specific fields change. This avoids unnecessary re-renders.

import { createForm } from 'final-form';

const form = createForm({
  onSubmit: values => console.log(values)
});

// Subscribe to field changes
const unsubscribe = form.subscribe(
  state => console.log('Field value:', state.values.email),
  { values: true }
);

formik stores all form state inside React component state (via useState or useReducer). Every field change triggers a full re-render of the form unless you optimize with React.memo.

import { useFormik } from 'formik';

function MyForm() {
  const formik = useFormik({
    initialValues: { email: '' },
    onSubmit: values => console.log(values)
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <input
        name="email"
        onChange={formik.handleChange}
        value={formik.values.email}
      />
    </form>
  );
}

react-hook-form embraces uncontrolled components by default. It uses refs to read input values directly from the DOM when needed, minimizing re-renders and avoiding state synchronization overhead.

import { useForm } from 'react-hook-form';

function MyForm() {
  const { register, handleSubmit } = useForm();

  return (
    <form onSubmit={handleSubmit(data => console.log(data))}>
      <input {...register('email')} />
    </form>
  );
}

✅ Validation: When and How Errors Are Checked

All three support synchronous and asynchronous validation, but their timing differs.

final-form validates on blur, change, or submit based on configuration. You provide validator functions that return error messages.

const form = createForm({
  validate: values => {
    const errors = {};
    if (!values.email) errors.email = 'Required';
    return errors;
  }
});

formik runs validation on every change by default (can be tuned). Validators can be functions or integrated with libraries like Yup.

import * as Yup from 'yup';

const formik = useFormik({
  validationSchema: Yup.object({
    email: Yup.string().required('Required')
  })
});

react-hook-form validates on blur, change, or submit (configurable per field). It supports both built-in rules and resolver-based validation (e.g., with Zod or Yup).

const { register } = useForm({
  defaultValues: { email: '' }
});

<input
  {...register('email', {
    required: 'Required',
    pattern: {
      value: /@/,
      message: 'Must be valid email'
    }
  })}
/>;

📦 Handling Complex Forms: Arrays and Nested Objects

Real forms often include lists (e.g., “add more contacts”) or deeply nested data.

final-form handles arrays via its FieldArray component. You manage array operations manually (push, remove, etc.).

import { FieldArray } from 'final-form-arrays';

<FieldArray name="contacts">
  {({ fields }) => (
    <div>
      {fields.map((name, index) => (
        <input key={name} name={`${name}.email`} />
      ))}
      <button onClick={() => fields.push({ email: '' })}>Add</button>
    </div>
  )}
</FieldArray>

formik provides useFieldArray (or FieldArray component) with built-in helpers for array manipulation.

import { useFieldArray } from 'formik';

function ContactList() {
  const { fields, append, remove } = useFieldArray({ name: 'contacts' });

  return (
    <div>
      {fields.map((field, index) => (
        <input key={field.id} name={`contacts[${index}].email`} />
      ))}
      <button onClick={() => append({ email: '' })}>Add</button>
    </div>
  );
}

react-hook-form offers useFieldArray with similar array helpers, but leverages uncontrolled inputs under the hood.

import { useFieldArray } from 'react-hook-form';

function ContactList({ control }) {
  const { fields, append, remove } = useFieldArray({
    control,
    name: 'contacts'
  });

  return (
    <div>
      {fields.map((field, index) => (
        <input key={field.id} {...register(`contacts.${index}.email`)} />
      ))}
      <button onClick={() => append({ email: '' })}>Add</button>
    </div>
  );
}

⚡ Performance Characteristics

final-form minimizes re-renders through fine-grained subscriptions. Only components listening to changed fields update.

formik re-renders the entire form on every keystroke by default. Mitigation requires manual memoization (React.memo, useCallback) or splitting into smaller components.

react-hook-form avoids most re-renders because inputs are uncontrolled. Only explicit state changes (like showing errors) trigger updates.

🔌 Integration with UI Libraries

All three work with popular component libraries (Material UI, Ant Design, etc.), but integration patterns differ:

  • final-form: Often used with react-final-form wrapper for React bindings.
  • formik: Directly compatible; many UI libraries provide Formik-specific adapters.
  • react-hook-form: Uses controller wrappers (Controller component or useController hook) for controlled third-party inputs.
// react-hook-form with Material UI
import { Controller } from 'react-hook-form';
import TextField from '@mui/material/TextField';

<Controller
  name="email"
  control={control}
  render={({ field }) => <TextField {...field} />}
/>;

🛠️ Developer Experience and Learning Curve

  • final-form: Steeper learning curve due to subscription model and separation from React state. Best for teams comfortable with external state management.
  • formik: Gentle onboarding for React developers — feels like “just more React.” Can become verbose in large forms.
  • react-hook-form: Requires shifting mindset to uncontrolled components. Very concise for simple forms, but needs extra setup for controlled inputs.

🔄 Migration and Ecosystem

  • final-form: Mature but less actively evolved. Still maintained, no deprecation notices.
  • formik: Actively maintained with strong community adoption. Regular updates and TypeScript support.
  • react-hook-form: Rapidly growing ecosystem, excellent TypeScript support, and frequent updates focused on performance and DX.

💡 When to Use Which?

  • Choose final-form if you’re building very large, performance-critical forms and are comfortable managing state outside React.
  • Choose formik if your team prefers a React-native approach, you’re already using Yup for validation, or you need quick prototyping.
  • Choose react-hook-form if you prioritize performance out of the box, want minimal re-renders, and are okay with uncontrolled inputs or wrapping controlled components.
How to Choose: react-hook-form vs formik vs final-form
  • react-hook-form:

    Choose react-hook-form if you prioritize performance and minimal re-renders by default, especially in forms with many fields. Its uncontrolled component model reduces boilerplate and aligns well with modern React practices. It's particularly strong when paired with validation resolvers like Zod or Yup, though you'll need to wrap controlled third-party inputs using its Controller API.

  • formik:

    Choose formik if your team prefers a straightforward, React-native approach using controlled components and component state. It integrates seamlessly with validation libraries like Yup and works well for small to medium forms where performance isn't the primary bottleneck. Avoid it for very large forms unless you're willing to invest in optimization techniques like memoization.

  • final-form:

    Choose final-form if you need maximum performance in large, complex forms and are comfortable managing form state outside of React's component tree. Its subscription model minimizes unnecessary re-renders, making it ideal for enterprise applications where form responsiveness is critical. However, be prepared for a steeper learning curve and more manual setup compared to React-centric alternatives.

README for react-hook-form

npm downloads npm npm Discord

Get started | API | Form Builder | FAQs | Examples

Features

Install

npm install react-hook-form

Quickstart

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>
  );
}

Sponsors

We’re incredibly grateful to these kind and generous sponsors for their support!

Past Sponsors

Thank you to our previous sponsors for your generous support!

Backers

Thanks go to all our backers! [Become a backer].

Contributors

Thanks go to these wonderful people! [Become a contributor].





Documentation website supported and backed by Vercel