React Hook Form Usage Guide
Forms are a common interaction method in user interfaces, and validating/managing their state can be challenging.
In the React ecosystem, React Hook Form
package name: react-hook-form
is a powerful solution for complex form management. To use it flexibly and appropriately, this guide summarizes common scenarios, usage patterns, easily overlooked points, and core principles. Please provide feedback if there are any omissions.
Usage🔨
In practical use, we often only use useForm/Controller/getValues
, but hook-form has other methods/configurations that are worth exploring.
mode in useForm
Different modes affect when form validation occurs, such as when errors are obtained after validation.
The default mode is onSubmit
export const VALIDATION_MODE = {
onBlur: 'onBlur',
onChange: 'onChange',
onSubmit: 'onSubmit',
onTouched: 'onTouched',
all: 'all',
} as const;
Note: mode affects validation strategy, but the form always detects value changes. Validation strategy before submitting behaviour.
reValidateMode
Note: If mode itself is configured as onChange, then reValidateMode becomes meaningless.
useFormContext
hook
- If a form is an N-level component tree, managing complex nested form items and passing form control parameters becomes problematic. We need to pass control/setValue etc. created by create form to child components, which then pass them down to lower-level components, creating a cumbersome chain.
If using useFormContext, you can directly manage form values within components without needing to pass them down layer by layer.
Layer-by-layer passing

Context
<FormProvider {...formProps}>
...
</FormProvider>
const {control} = useFormContext();
useFieldArray
hook
- Sometimes data exists in array/List format. For dynamic item addition, useFieldArray is more convenient.
const { fields, append, prepend, remove, swap, move, insert } = useFieldArray(
{
control, // control props comes from useForm (optional: if you are using FormProvider)
name: 'test' // unique name for your Field Array
}
);
Note: You can still use arrays without useFieldArray, but it’s less convenient
<Form.Item label={'Person 0'}>
<input
{...register('persons.0', {
})}
/>
</Form.Item>
watch
watchfunc
- useWatch is used to monitor specific field changes or all current field values, but if you want to know which field changed each time or perform batch watching for logic processing, you can use the watch method.
useEffect(() => {
const wFn = watch((data, {name}) => {
console.log('column changed', data, name) // data contains the latest values after modification
});
return wFn.unsubscribe;
}, [])
Note: Remember to unsubscribe when subscribing.
watch vs useWatch
React-hook-form has two watch methods: useWatch and watch. useWatch is a hook method, watch is a function method. For single field subscriptions, you can use either watch or useWatch, but useWatch is recommended. WHY?
const allFieldWatch = useWatch({
control,
name:['price'], // If name is not provided, it watches all fields
})
const priceWatch = watch(['price']);
register
registerfunc
- Registers a form field and returns an object. This method is primarily for native form elements. For non-native form elements like Input components in Tea component libraries, be cautious about potential issues with onChange methods.
<input
{...register('address', {
validate: (s) => {
console.log('address validate', s);
}
})}
/>
Note:
- name supports nested notation, such as
persons.0
,address.city
. - For arrays, you can directly register with non-zero indices.
register vs Controller
As mentioned above, register is suitable for native elements, while Controller is suitable for higher-order form components, such as third-party components.
<Form.Item label={'Person 0'}>
<input
{...register('persons.0', {
// valueAsNumber: true,
})}
/>
</Form.Item>
<Form.Item label={'Person 1'}>
<Input
{...register('persons.1', {
// valueAsNumber: true,
})}
/>
</Form.Item>
PS: The official documentation prefers using register
.
Controller vs useController
Controller essentially calls useController, so they are the same - just different usage patterns: one is a component tag, the other is a hook function.
const Controller = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
TTransformedValues = TFieldValues,
>(
props: ControllerProps<TFieldValues, TName, TTransformedValues>,
) =>
props.render(useController<TFieldValues, TName, TTransformedValues>(props));
export { Controller };
resetfunc
/ setValue
If reset method doesn’t specify field values, it resets to the values set in defaultValues.
reset();
reset operation resets all fields, not just specific ones. For example, if reset only specifies field A, other fields will become undefined.
To reset a single field, you should use setValue method or carry all field values when resetting
reset({ ...getValues(), price: 111 })
Note: There is setValue but no setValues, so to implement multiple values with setValue, you need to handle it yourself through iteration
shouldDirty
The second parameter in setValue has shouldDirty, indicating whether to set the dirty state. When set to true, the form will check the dirty state to determine updates to dirtyFields. For example, if there’s no change compared to defaultValues, dirtyFields will have values.
Note: After reset, defaultValues may be updated to the newly set values.
valueAsNumber/valueAsDate
<input
{...register('quantity', {
// valueAsNumber: true,
})}
/>
// "quantity": "2121212121"
// "quantity": 2121212121
If using Controller approach, you need to implement it yourself, such as field.onChange(Number(value))
dirtyFields
formState contains dirtyFields, indicating which fields in the current form have been modified. Use this property when you need to determine which form fields have been changed.
```shell
const {isDirty, dirtyFields} = useFormState({
control
});
Official Recommendations - Practice👊
transform/parse
transform and parse mentioned in react-hook-form are just practices, not built-in functionality.
const form_fields = [
{
name: 'price',
transform: {
output: v => +v
}
},
{
name: 'num'
},
{
name: 'quantity'
}
]
{
form_fields.map(item => <Form.Item label={item.name}>
<Controller key={item.name} render={({field}) => <Input {...field}
onChange={v => {
return field.onChange(item?.transform?.parse ? item.transform.output(v) : v);
}}
/>} name={item.name}
control={control}/>
</Form.Item>)
}
Integration with validation libraries like yup
Common libraries include yup/Joi/Superstruct/zod.
const schema = yup
.object()
.shape({
price: yup.number().required(),
quantity: yup.number().min(1).max(100).required(),
})
.required();
const formProps = useForm({
...,
resolver: yupResolver(schema),
});
Note: Validation triggering still depends on the mode setting.
Common Issues❓
setValue with null values
When setting values to null/undefined, controlled form components may not render updates properly.
This is due to React’s controlled component handling mechanism, not special handling by hook-form.
setValue('price', null, {
shouldDirty: true,
});
const [inputValue, setInputValue] = useState(1_0);
<div>
<label>
<span>Name</span>
<input value={inputValue}/>
</label>
<button onClick={() => {
setInputValue(5);
}}>
Set to empty
</button>
</div>
Solution
For example, handle it during rendering like this.
<input value={inputValue??''}/>
Source Code👀
Here we examine some parts of hook-form’s source code to answer certain questions.
Package dependencies - zero dependency
React-hook-form has no dependencies, only peerDependencies. So can any React project use react-hook-form normally???
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18 || ^19"
},
For example, the WeChat mini-program development framework taro uses React for development. Can react-hook-form be used for form validation if needed?
Answer: YES
.
Sample Taro mini-program code, using taro-ui for UI form components:
const { control } = useForm({
mode: 'onChange',
});
...
<Form>
<Controller
control={control}
name="name"
render={({ field }) => <AtInput {...field} title="Name"/>}
/>
<Controller
control={control}
name="test"
render={({ field }) => <AtInput {...field} title="Test Input"/>}
/>
</Form>
resolver principle
- React hook form can integrate with different schema validation libraries because there’s a separate package - @hookform/resolvers that provides adapter support for various libraries.

...
if (result.error) {
return {
values: {},
errors: toNestErrors(
parseErrorSchema(
result.error,
!options.shouldUseNativeValidation &&
options.criteriaMode === 'all',
),
options,
),
};
}
...
Good performance?
- Avoid unnecessary re-renders.
- Use native form registration mechanism + ref management.
Controller
implements minimal rendering for controlled components.- Destructure state as needed, rather than reading everything at once.
useWatch principle
Essentially calls form.control’s subscribe method in the hook’s effect to monitor form value changes. Once values change, it updates the value object.
export function useWatch<TFieldValues extends FieldValues>(
props?: UseWatchProps<TFieldValues>,
) {
...
React.useEffect(
() =>
control._subscribe({
name: _name.current as InternalFieldName,
...
callback: (formState) =>
!disabled &&
updateValue(
generateWatchOutput(
_name.current as InternalFieldName | InternalFieldName[],
control._names,
formState.values || control._formValues,
false,
_defaultValue.current,
),
),
}),
[control, disabled, exact],
);
const [value, updateValue] = React.useState(
control._getWatch(
name as InternalFieldName,
defaultValue as DeepPartialSkipArrayKey<TFieldValues>,
),
);
...
return value;
}
Additional Information🔗
When was react-hook-form released?
Research shows that react-hook-form released its first version on Mar 3, 2019 and is currently actively maintained.
Package size
Current version: v7.56.3
, with GZIP compressed package size of approximately 11KB
.
Support for third-party UI form components
Such as tea-component, mui, antd.
Other form solution options
Related links
- https://react-hook-form.com/
- https://github.com/orgs/react-hook-form/discussions/2704
- https://www.reddit.com/r/react/comments/1ed4sdj/controller_wrapper_vs_register_utility_function/
- https://npmtrends.com/formik-vs-react-final-form-vs-react-hook-form
- https://github.com/react-hook-form/react-hook-form/pull/4825
- https://juejin.cn/post/7423261843933675558
Final Thoughts
React Hook Form provides an excellent solution for form management in React applications, offering performance optimizations, flexibility, and a clean API. Its zero-dependency approach and small bundle size make it suitable for various projects, from web applications to mini-programs. By understanding its core concepts like validation modes, field registration, and state management, developers can build robust and efficient forms that provide great user experiences.