final-form vs formik vs react-hook-form
React Form State Management Libraries
final-formformikreact-hook-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
final-form03,044382 kB9910 months agoMIT
formik034,380585 kB8375 months agoApache-2.0
react-hook-form044,6401.28 MB13812 days 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: final-form vs formik vs react-hook-form

  • 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.

  • 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.

  • 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.

README for final-form

You build great forms, but do you know HOW users use your forms? Find out with Form Nerd! Professional analytics from the creator of Final Form.


πŸ’° Hey there! Do you fancy yourself a javascript expert? Take this quiz and get offers from top tech companies! πŸ’°


🏁 Final Form

Final Form

Backers on Open Collective Sponsors on Open Collective NPM Version NPM Downloads Build Status codecov.io styled with prettier

βœ… Zero dependencies

βœ… Framework agnostic

βœ… Opt-in subscriptions - only update on the state you need!

βœ… πŸ’₯ 5.1k gzipped πŸ’₯


Final Form is sponsored by Sencha.

Comprehensive JS framework and UI components for building enterprise-grade web apps.


πŸ’¬ Give Feedback on Final Form πŸ’¬

In the interest of making 🏁 Final Form the best library it can be, we'd love your thoughts and feedback.

Take a quick survey.


Get Started

Philosophy

Examples

API

Companion Libraries

Who's using Final Form?