React Hook Form
React Hook Form is a high-performance, flexible, and extensible forms library for React applications, currently stable at version 7.72.1. It leverages an uncontrolled component approach, minimizing re-renders and improving performance by isolating component updates from form state changes. The library maintains a rapid release cadence, frequently delivering patches and minor versions, with a beta for version 8 already available. Key differentiators include its focus on performance, small bundle size, seamless integration with native HTML form validation, and robust TypeScript support for enhanced type safety across form values and errors. It provides essential hooks like `useForm` for comprehensive form management, `Controller` for integrating with external controlled components, and `useFieldArray` for dynamic lists of inputs, making it suitable for a wide spectrum of form complexities.
Common errors
-
TypeError: Cannot read properties of undefined (reading '_f')
cause This error often occurs when attempting to access internal `_f` property of a field that hasn't been properly registered with `react-hook-form` or has been unmounted. It can also happen when `Controller` or `useFieldArray` are misused or when the `control` object is not propagated correctly.fixEnsure all input fields are correctly registered using `register` or wrapped with `Controller`. Verify that the `control` object from `useForm` is passed down to `Controller` and `useFieldArray` components, especially in nested contexts or when using `FormProvider`. Check for any conditional rendering that might unmount fields unexpectedly without proper unregistration handling. -
formState.isValid incorrect on Controller re-mount
cause In specific scenarios, `formState.isValid` may not update correctly when a `Controller`-managed component re-mounts, leading to an inaccurate validation status of the form.fixThis specific issue was addressed in `v7.72.1`. Upgrade to `react-hook-form@7.72.1` or a newer version to receive the fix. -
Field array ghost elements with keepDirtyValues
cause Older versions of `useFieldArray` could sometimes produce 'ghost' elements or exhibit incorrect dirty state behavior when `keepDirtyValues` was enabled, especially after manipulation (append, remove, etc.).fixThis was addressed in `v7.70.0`. Update to `react-hook-form@7.70.0` or a newer version to resolve issues with field array ghost elements and `keepDirtyValues`. -
The first keystroke is not working, or input value is reset.
cause This typically happens if you are mistakenly using the `value` prop directly on an uncontrolled input that `react-hook-form` manages, instead of `defaultValue`. `React Hook Form` relies on uncontrolled inputs, where it reads the initial value from `defaultValue` and then tracks changes internally, not through React state for every keystroke.fixReplace `value="..."` with `defaultValue="..."` for initial values on your inputs registered with `react-hook-form`. Avoid using `value` for managed inputs unless you are specifically working with controlled components via `Controller`.
Warnings
- breaking Version 8.0.0-beta.1 introduces breaking changes: `register` now expects an input `ref` directly instead of a partial object. The `useFieldArray` hook has renamed its `id` property to `key` and removed the `keyName` prop.
- breaking Critical RCE (Remote Code Execution) vulnerabilities (CVE-2025-55182, CVE-2025-55183, CVE-2025-55184, CVE-2025-67779) were listed in v7.69.0. While the specific impact on `react-hook-form` is not fully detailed in the provided excerpt, such CVEs in dependencies or related ecosystems (like React Server Components as mentioned in general CVE discussions) can indicate severe security risks. Users should prioritize updating and reviewing their dependency tree.
- gotcha When using `setValue` with `shouldDirty` (or when relying on `isDirty` with numeric string keys in `defaultValues`), older versions may incorrectly mark unrelated fields as dirty or fail to correctly track dirtiness. This can lead to unexpected form state behavior.
- gotcha Providing `defaultValues` to `useForm` is crucial for `isDirty` and `dirtyFields` to work correctly. Without `defaultValues`, the library has no baseline to compare against, leading to `isDirty` always being `false` or `dirtyFields` being empty even after user interaction.
Install
-
npm install react-hook-form -
yarn add react-hook-form -
pnpm add react-hook-form
Imports
- useForm
const { useForm } = require('react-hook-form');import { useForm } from 'react-hook-form'; - Controller
import Controller from 'react-hook-form/dist/Controller';
import { Controller } from 'react-hook-form'; - useFieldArray
import { UseFieldArray } from 'react-hook-form';import { useFieldArray } from 'react-hook-form'; - FormProvider
import { FormProvider } from 'react-hook-form';
Quickstart
import React from 'react';
import { useForm } from 'react-hook-form';
type FormData = {
firstName: string;
lastName: string;
email: string;
age: number;
};
export default function MyForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
defaultValues: {
firstName: '',
lastName: '',
email: '',
age: 18,
},
});
const onSubmit = (data: FormData) => {
console.log('Form Submitted:', data);
// In a real app, you would send this data to an API
// Example: fetch('/api/submit-form', { method: 'POST', body: JSON.stringify(data) });
alert(JSON.stringify(data, null, 2));
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4 p-4 max-w-md mx-auto bg-white shadow-md rounded-lg">
<div>
<label htmlFor="firstName" className="block text-sm font-medium text-gray-700">First Name:</label>
<input
id="firstName"
{...register('firstName', { required: 'First name is required' })}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
/>
{errors.firstName && <p className="mt-1 text-sm text-red-600">{errors.firstName.message}</p>}
</div>
<div>
<label htmlFor="lastName" className="block text-sm font-medium text-gray-700">Last Name:</label>
<input
id="lastName"
{...register('lastName', { required: 'Last name is required' })}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
/>
{errors.lastName && <p className="mt-1 text-sm text-red-600">{errors.lastName.message}</p>}
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">Email:</label>
<input
id="email"
type="email"
{...register('email', {
required: 'Email is required',
pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, message: 'Invalid email address' }
})}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
/>
{errors.email && <p className="mt-1 text-sm text-red-600">{errors.email.message}</p>}
</div>
<div>
<label htmlFor="age" className="block text-sm font-medium text-gray-700">Age:</label>
<input
id="age"
type="number"
{...register('age', { required: 'Age is required', min: { value: 18, message: 'Must be at least 18' } })}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
/>
{errors.age && <p className="mt-1 text-sm text-red-600">{errors.age.message}</p>}
</div>
<button
type="submit"
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Submit
</button>
</form>
);
}