⏱️ Estimated read: 4 min
|
|
useForm Options
| Option | Type | Default | Notes |
|---|
resolver | Resolver | — | Schema validation (Zod, Yup); mutually exclusive with validate |
mode | 'onSubmit' | 'onChange' | 'onBlur' | 'all' | 'onSubmit' | When to validate |
reValidateMode | 'onChange' | 'onBlur' | 'onChange' | Re-validation after first error |
defaultValues | object | () => object | Promise<object> | {} | Required for dirty tracking and controlled inputs |
values | object | — | Controlled form values; updates form when data changes |
shouldUnregister | boolean | false | Remove values on unmount; incompatible with useFieldArray |
shouldFocusError | boolean | true | Focus first invalid field on submit |
criteriaMode | 'firstError' | 'all' | 'firstError' | Return first or all errors per field |
delayError | number | — | Delay error display in ms |
shouldUseNativeValidation | boolean | false | Enable browser native validation (v7.9+) |
progressive | boolean | false | Forward rules as native HTML attributes (v7.44+) |
disabled | boolean | false | Disable entire form (v7.48+) |
register Validation Rules
| Rule | Type | Example | Notes |
|---|
required | boolean | string | required: "Email is required" | String becomes message |
min / max | number | { value, message } | min: { value: 18, message: "18+" } | Number comparison |
minLength / maxLength | { value, message } | minLength: { value: 8, message: "8+" } | String length |
pattern | RegExp | { value, message } | pattern: { value: /^\S+@\S+$/, message: "Invalid" } | Avoid /g flag; it tracks lastIndex |
validate | Function | Record<string, Function> | validate: (v) => v !== "admin" || "Reserved" | Receives value; v7.42+ also full form values |
valueAsNumber | boolean | valueAsNumber: true | Returns NaN on failure |
valueAsDate | boolean | valueAsDate: true | Returns Invalid Date on failure |
setValueAs | (value: any) => T | setValueAs: (v) => v.trim() | Ignored if valueAsNumber or valueAsDate is true |
disabled | boolean | disabled: true | Returns undefined; skips validation (v7.13+) |
deps | string | string[] | deps: ["password"] | Re-validate listed fields when this field validates (v7.14+) |
shouldUnregister | boolean | shouldUnregister: true | Field-level unmount behavior; avoid with useFieldArray |
onChange | (e) => void | onChange: (e) => log(e.target.value) | Called on change event (v7.16+) |
onBlur | (e) => void | onBlur: (e) => log(e.target.value) | Called on blur event |
formState Properties
| Property | Type | Meaning | Notes |
|---|
errors | FieldErrors | Validation messages per field | Use ErrorMessage component or read manually |
isDirty | boolean | Any field differs from defaultValues | Requires defaultValues |
dirtyFields | object | Which fields changed | Field-level; does not equal isDirty |
touchedFields | object | Which fields blurred | — |
defaultValues | object | Current defaults (v7.37+) | Updated via reset |
isSubmitted | boolean | At least one submit attempted | Stays true until reset() |
isSubmitSuccessful | boolean | Last submission succeeded | No runtime error |
isSubmitting | boolean | onSubmit promise is pending | Must await or return the promise |
isLoading | boolean | Async defaultValues loading (v7.41+) | Only for async defaults |
submitCount | number | Total submit attempts | — |
isValid | boolean | No errors | setError forces false immediately |
isValidating | boolean | Validation in progress | — |
validatingFields | object | Fields in async validation (v7.51+) | — |
disabled | boolean | Form disabled via useForm (v7.48+) | — |
isReady | boolean | Subscription setup ready (v7.56+) | Use before setValue in children |
Core Methods
| Method | Signature | Notes |
|---|
register | register("name", rules) | Spread onto input: {...register("email")} |
handleSubmit | handleSubmit(onValid, onInvalid?) | Wraps form onSubmit |
watch | watch("name") / watch(["a","b"]) / watch() | Re-renders calling component; isolate to specific field |
useWatch | useWatch({ control, name }) | Re-renders only the component that calls it |
getValues | getValues("name") / getValues() | No re-render; read in handlers |
setValue | setValue("name", v, { shouldValidate, shouldDirty, shouldTouch }) | Programmatic set |
reset | reset(values?, { keepErrors, keepDirty, keepIsSubmitted, keepTouched, keepIsValid, keepSubmitCount }) | Back to defaultValues |
trigger | trigger("name"?) / trigger(["a","b"]) | Manual validation; returns Promise |
setError | setError("name", { type, message }) | Server-side errors; forces isValid false |
clearErrors | clearErrors("name"?) | Remove errors |
setFocus | setFocus("name") | Focus a field |
unregister | unregister("name") | Remove field registration |
useFieldArray (v7)
| Item | Syntax | Notes |
|---|
| Hook | const { fields, append, prepend, insert, remove, swap, move, update, replace } = useFieldArray({ control, name }) | control required |
| Render key | key={field.id} | Never use index |
| Append | append({ value: "" }) | Cannot be empty object |
| Prepend | prepend({ value: "" }) | Add at start |
| Insert | insert(index, { value: "" }) | Insert at position |
| Remove | remove(index) | — |
| Swap | swap(from, to) | Swap two rows |
| Move | move(from, to) | Move row |
| Update | update(index, { value: "" }) | Unmounts and remounts; prefer setValue for simple updates |
| Replace | replace(newArray) | Overwrite entire array |
| Row type | Must be object (v7) | { value: string }[], not string[] |
shouldUnregister | Not supported | Do not combine with useFieldArray |
watch vs useWatch vs subscribe vs getValues
| API | Re-renders | Use For |
|---|
watch | The component calling useForm | Simple form-level display; expensive on large forms |
useWatch | Only the component calling it | Deep rendering: live totals, conditional fields |
subscribe | Nothing | Side effects: autosave, analytics, external sync |
getValues | Nothing | On-demand reads in event handlers |
useFormState | When subscribed state changes | errors, isDirty, isSubmitting (not values) |
Controller / useController
| Item | Notes |
|---|
| Hook | const { field, fieldState, formState } = useController({ name, control, rules }) |
field.onChange | Send value to RHF |
field.onBlur | Mark touched |
field.value | Current value |
field.name | Field name |
field.ref | Required for focus management |
field.disabled | Disabled state |
fieldState.invalid | Error boolean |
fieldState.error | Error object |
fieldState.isTouched | Touched state |
fieldState.isDirty | Dirty state |
fieldState.isValidating | Async validation in progress |
Error Display Patterns
| Pattern | Attribute | Use |
|---|
| Visible message | errors.field?.message | Render in <p> below input |
| Screen reader state | aria-invalid={!!errors.field} | Announces invalid state |
| Error linkage | aria-describedby={errors.field ? "field-error" : undefined} | Links message to input |
| Server error | setError("field", { type: "server", message: "..." }) | After API response |
| Root error | setError("root.serverError", { message: "..." }) | Form-level error |
| Immediate announce | role="alert" on error message | Single errors only; noisy on submit with many |
Controlled Input Patterns
| Input Type | Pattern |
|---|
| Native text/number | {...register("name")} |
| Checkbox | {...register("name")} |
| Radio group | Same name, one register per input |
| Select | {...register("name")} |
| Third-party UI | <Controller> with field spread |
| File | register + watch for preview |
| Nested object | register("user.address.city") |
| Array field | register(\items.${index}.name`)` |
| Flat array (v7) | Not supported; use { value: ... } objects |
v8 Beta Deltas
| Pattern | v7 | v8 |
|---|
| Field array key | field.id | field.key |
| Array overwrite | setValue("items", arr) | replace(arr) |
| Watch callback | watch((v) => ...) | subscribe({ formState: { values: true }, callback }) |
| Method refs | Stable | New ref on state change; remove from effect deps |
| Callback params | any | unknown; annotate types |
| Nested errors | formState from useFormContext() | useFormState({ control }) |
<Watch /> prop | names | name |
| Flat field arrays | Not supported | Supported (primitive rows) |
Quick How-To Guides & Fast FixesNeed a quick fix or step-by-step solution? Browse our fast answers on HowTo.KandZ.me:
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!