NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #750 most downloaded on npm
Performant, flexible and extensible forms library for React Hooks
Last release 9 days ago
26 Sep 2026
Ships on a steady schedule
a new release about every 2 weeks
Nearly every release is documented
notes for 60 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
8 years old
1100 releases · first in 2019
Nothing published for this version
Breaking change: setError's shouldFocus option has been moved into the third argument.
setError's shouldFocus option has been moved into the third argument.- setError('test', { type: 'type', message: 'issue', shouldFocus: true })
+ setError('test', { type: 'type', message: 'issue' }, { shouldFocus: true })
- UseFormMethods
+ UseFormReturn
- UseFormOptions
+ UseFormProps
- UseFieldArrayMethods
+ UseFieldArrayReturn
- UseFieldArrayOptions
+ UseFieldArrayProps
- UseControllerMethods
+ UseControllerReturn
- UseControllerOptions
+ UseControllerProps
- ArrayField
+ FieldArray
setValue with Controller and reset with useFieldArray issues: 4111 & 4108 (#4113)One column per quarter.
Breaking change: improve field array remove result and no longer remove field array value after unmount
field name reference will be removed with unregister (#4010)
Breaking change: improve field array remove result and no longer remove field array value after unmount
const { remove } = useFieldArray({ name: 'test' })
remove();
getValues(); // V6: result form value {}
getValues(); // V7: result form value { test: [] }
Set (#4015)onChange perf with `resolver (#4017)Breaking change: No longer support IE 11 support
useFormState (#3740)const { isDirty, errors } = useFormState();
watch support can subscribe to the entire form with a callbackwatch((data, { name, type }) => {
console.log('formValue', data);
console.log('name', name);
console.log('type', type);
});
useController includes new isValidating state (#3778)useController includes new error state (#3921)const {
meta: { error, isValidating },
} = useController({ name: 'test' });
unregister second argument (#3964)unregister('test', { keepDirty: true });
field being validated (#3881)- resolver: (values: any, context?: object) => Promise<ResolverResult> | ResolverResult
+ resolver: (
+ values: any,
+ context?: object,
+ options: {
+ criteriaMode?: 'firstError' | 'all',
+ names?: string[],
+ fields: { [name]: field } // Support nested field
+ }
+ ) => Promise<ResolverResult> | ResolverResult
useFieldArray action can focus input by name and indexappend(object, config: { shouldDirty: boolean, focusIndex: number, focusName: string })
insert(object, config: { shouldDirty: boolean, focusIndex: number, focusName: string })
prepend(object, config: { shouldDirty: boolean, focusIndex: number, focusName: string })
Breaking change: No longer support IE 11 support
Breaking change: register has been changed from register at ref to a function which needs to be spread as props.
- <input ref={register, { required: true }} name="test" />
+ <input {...register('name', { required: true })} />
+ <TextInput {...register('name', { required: true })} />
name with array will only support dot syntax instead of brackets.- test[2].test
+ test.2.test
as prop at Controller and fix render prop consistency (#3732)- <Controller render={props => <input {...props} />} />
+ <Controller render={({ field }) => <input {...field} />} />
errors alias (#3737)- const { errors } = useForm();
+ const { formState: { errors } } = useForm();
reset second argument (#3905)- reset({}, { isDirty: true })
+ reset({}, { keepIsDirty: true })
touched to touchedFields for consistency (#3923)- const { formState: { touched } } = useForm();
+ const { formState: { touchedFields }} = useForm();
trigger will no longer return validation result.- await trigger('test') // return true or false
+ trigger('test') // void
remove isSubmitting proxy (#4000)
input register will no longer be removed due to unmount, user will have to manually invoke unregister
useWatch internal mechanism improvement (#3754)Controller and useController apply useFormState internally and improve performance (#3778)register type support for input name (#3738)Controller and useCOntroller type support for input name (#3738)useFieldArray internal logic and data structure improvement (#3858)useFieldArray internal fields update with subscription (#3943)useWatch type improvement (#3931)const (#3920)useFieldArray internal type structure (#3986)MutationObserver removed from useForm🐛 fix #5506 watch return null value with field array
🐛 fix #5506 watch return null value with field array
🐞 V6: useFieldArray with nested useFieldArray renders with fields
🐞 V6: useFieldArray with nested useFieldArray renders with fields
🐞 fix #4792 issue with nested field array append
🐞 fix #4792 issue with nested field array append
🐞 fix issue with setInternalValue() for array values (#4402) 🐞 fix: prevent cloning objects for class-based instances (#4400) 🐞 fixing Field Array's r
🐞 fix issue with setInternalValue() for array values (#4402) 🐞 fix: prevent cloning objects for class-based instances (#4400) 🐞 fixing Field Array's remove function (#4389)
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
radio input default selection will return null instead of empty string ''
null instead of empty string ''valueAsNumber with empty input will return NaN instead of 0Nothing published for this version
Nothing published for this version
setValue without shouldUnregister:false will no longer deep clone its value instead with shallow clone
setValue without shouldUnregister:false will no longer deep clone its value instead with shallow cloneisValidating, this will set to true during validation.const {
formState: { isValidating },
} = useForm();
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
When invoking reset({ value }) value will be shallow clone value object which you have supplied instead of deepClone.
reset({ value }) value will be shallow clone value object which you have supplied instead of deepClone.// ❌ avoid the following with deep nested default values
const defaultValues = { object: { deepNest: { file: new File() } } };
useForm({ defaultValues });
reset(defaultValues); // share the same reference
// ✅ it's safer with the following, as we only doing shallow clone with defaultValues
useForm({ deepNest: { file: new File() } });
reset({ deepNest: { file: new File() } });
useController: This custom hook is what powers Controller, and shares the same props and methods as Controller. It's useful to create reusable Controlled input, while Controller is the flexible option to drop into your page or form.import React from 'react';
import { TextField } from '@material-ui/core';
import { useController } from 'react-hook-form';
function Input({ control, name }) {
const {
field: { ref, ...inputProps },
meta: { invalid, isTouched, isDirty },
} = useController({
name,
control,
rules: { required: true },
defaultValue: '',
});
return <TextField {...inputProps} inputRef={ref} />;
}
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
defaultValues is required to measure isDirty, keep a single source of truth to avoid multiple issues raised around isDirty
defaultValues is required to measure isDirty, keep a single source of truth to avoid multiple issues raised around isDirtywatch with useFieldArray, fields object is no longer required as defaultValue- watch('fieldArray', fields);
+ watch('fieldArray');
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Controller will have an extra ref props to improve DX in terms of focus management.
Controller will have an extra ref props to improve DX in terms of focus management.<Controller
name="test"
render={(props) => {
return (
<input
value={props.value}
onChange={props.onChange}
ref={props.ref} // you can assign ref now without the use of `onFocus`
/>
);
}}
/>
// focus will work correct without the `onFocus` prop
<Controller name="test" as={<input />} />
resolver with group error object will no longer need with trigger to show and clear error. This minor version made hook form look at parent error node to detect if there is any group error to show and hide.const schema = z.object({
items: z.array(z.boolean()).refine((items) => items.some((item) => item)),
});
{
items.map((flag, index) => (
<input
type="checkbox"
defaultChecked={false}
// onChange={() => trigger("items")} now can be removed
ref={register}
name={`items.${index}`}
/>
));
}
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
with shouldUnregister set to false, empty Field Array will default [] as submission result.
const { handleSubmit } = useForm({
shouldUnregister: false,
});
useFieldArray({
name: 'test',
});
handleSubmit((data) => {
// shouldUnregister: false
// result: { data: {test: []} }
// shouldUnregister: true
// result: {}
});
Nothing published for this version
Nothing published for this version
when input unmounts touched and dirtyFields will no longer get removed from formState (shouldUnregister: true).
touched and dirtyFields will no longer get removed from formState (shouldUnregister: true).Nothing published for this version
Nothing published for this version
Nothing published for this version
new formState isSubmitSuccessful to indicate successful submission
isSubmitSuccessful to indicate successful submissionsetError now support focus on the actual inputsetError('test', { message: 'This is required', shouldFocus: true });
shouldUnregister:false defaultValues data will be part of the submission datashouldUnregister:false conditional field is going to work with useFieldArraysetValue now support useFieldArray- setValue('test', 'data')
+ setValue('test', [{ test: '123' }]) // make it work for useFieldArray and target a field array key
exact config at clearErrors- clearErrors('test', { exact: false })
+ clearErrors('test') // does it automatically in the lib
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →