Imported from doxynix/doxynix (
.agents/skills/react-hook-form/AGENTS.md). Install upstream withnpx skills add doxynix/doxynix --skill react-hook-form. Copyright stays with the author.
React Hook Form
Version 2.1.0
Community
July 2026
Note: This document targets React Hook Form codebases. It is mainly for agents and LLMs to follow when maintaining, generating, or refactoring forms. Humans may also find it useful, but guidance here is optimized for automation and consistency by AI-assisted workflows.
Abstract
Focused guide to the React Hook Form decisions a capable model gets wrong. Contains 35 rules across 7 categories, verified against react-hook-form 7.82.0 by diffing the shipped type definitions and type-checking every code block under tsc --strict against the real package types. Covers where a subscription must live to isolate re-renders, the third useForm generic that transforming resolvers require, the options that silently drop data from the submitted payload (register disabled, shouldUnregister, useFieldArray disabled), the NaN that valueAsNumber produces for an empty input, server error handling via setError('root.*'), resetDefaultValues() for rebasing the dirty baseline after a save, and the Watch / FormStateSubscribe / FieldArray render-prop components. Rules that merely restate what the library already does correctly have been removed.
Table of Contents
- Form Configuration — CRITICAL
- 1.1 Always Provide defaultValues for Form Initialization — CRITICAL (prevents uncontrolled-to-controlled input warnings and a reset() with nothing to restore)
- 1.2 Depend on formState Slices, Not on formState Itself — HIGH (prevents effects that re-run on every keystroke)
- 1.3 Justify Any mode Other Than the Default onSubmit — CRITICAL (prevents a full validation pass and re-render on every keystroke)
- 1.4 Keep Default reValidateMode Unless Validation Is Expensive — MEDIUM (maintains immediate corrective feedback after first submit)
- 1.5 Keep shouldUnregister Off Unless Hidden Fields Must Leave the Payload — HIGH (prevents silently dropping values the user already entered)
- 1.6 Pass the Third useForm Generic When the Resolver Transforms Values — CRITICAL (makes handleSubmit receive the schema's output type instead of its input type)
- 1.7 Use Async defaultValues for Server Data — CRITICAL (eliminates manual useEffect reset patterns)
- 1.8 Use the HTML disabled Attribute for Visual Disabling, Not register's disabled Option — MEDIUM (prevents fields silently missing from submission and skipped validation)
- 1.9 Use the values Prop to Keep a Form in Sync with Server Data — HIGH (replaces a useEffect+reset that overwrites edits whenever the query refetches)
- Field Subscription — CRITICAL
- 2.1 Avoid Calling watch() in Render for One-Time Reads — HIGH (prevents unnecessary subscriptions and re-renders)
- 2.2 React.memo Cannot Stop Context-Driven Re-renders Under FormProvider — MEDIUM (replaces a memo pass that has no effect with isolation that does)
- 2.3 Use subscribe() to React to Form Changes Outside the React Lifecycle — HIGH (eliminates re-renders for non-UI consumers like analytics, autosave, telemetry)
- 2.4 Use the Render-Prop Components to Isolate Re-renders Without a Child Component — HIGH (confines a subscription to one subtree without authoring a wrapper component)
- 2.5 Use useFormContext Sparingly for Deep Nesting — MEDIUM (reduces prop drilling but increases implicit dependencies)
- 2.6 Use useWatch Instead of watch for Isolated Re-renders — CRITICAL (confines value-change re-renders to the subscribing component)
- 2.7 Watch Specific Fields Instead of Entire Form — CRITICAL (reduces re-renders from N fields to 1 field change)
- Controlled Components — HIGH
- 3.1 Isolate Controlled Inputs in Dedicated Child Components — HIGH (re-renders only the changed field instead of the whole form)
- 3.2 Wire Controller Field Props Correctly for UI Libraries — HIGH (prevents a control that renders correctly but never writes back to the form)
- Validation Patterns — HIGH
- 4.1 Build the Validation Schema Once, Outside the Render Path — HIGH (stops rebuilding the whole schema object on every keystroke)
- 4.2 Handle the NaN valueAsNumber Produces for an Empty Input — HIGH (prevents an optional number field that can never be left blank)
- 4.3 Surface Server Errors via setError('root.serverError', ...) — HIGH (prevents lost server-side validation errors and unrecoverable form state)
- 4.4 Use delayError to Debounce Rapid Error Display — MEDIUM (reduces UI flicker during fast typing validation)
- State Management — MEDIUM-HIGH
- 5.1 Avoid isValid with onSubmit Mode for Button State — MEDIUM (prevents whole-form validation on every change under a deferred-validation mode)
- 5.2 Read Every formState Property You Depend On During Render — MEDIUM (prevents a component that never re-renders when the state it shows changes)
- 5.3 Rebase Defaults with resetDefaultValues After a Successful Save — HIGH (clears isDirty without discarding edits made during the in-flight request)
- 5.4 Use handleSubmit's Second Argument to Handle a Rejected Submit — MEDIUM (gives a failed submit somewhere to go instead of silently doing nothing)
- 5.5 Use useFormState for Isolated State Subscriptions — MEDIUM (prevents parent re-renders from state access in children)
- 5.6 Wrap Async Submit Handlers in try/catch and Reset on isSubmitSuccessful — HIGH (prevents stuck isSubmitting state and missing post-success reset)
- Field Arrays — MEDIUM-HIGH
- 6.1 Separate Sequential Field Array Operations — MEDIUM-HIGH (prevents state corruption from batched mutations)
- 6.2 Use field.id as Key in useFieldArray Maps — MEDIUM-HIGH (prevents state corruption and unnecessary re-renders)
- 6.3 Use Single useFieldArray Instance Per Field Name — MEDIUM-HIGH (prevents state conflicts from duplicate subscriptions)
- 6.4 useFieldArray's disabled Option Makes Every Mutation a Silent No-op — MEDIUM-HIGH (prevents append/remove calls that vanish with no error or warning)
- Integration Patterns — MEDIUM
- 7.1 Transform Values at Controller Level for Type Coercion — MEDIUM (stops string input values reaching a number- or date-typed schema)
- 7.2 Verify shadcn Form Component Import Source — MEDIUM (prevents silent component mismatch bugs)
- 7.3 Wire shadcn Select with onValueChange Instead of Spread — MEDIUM (prevents a Radix Select that renders but never writes to the form)
References
- https://react-hook-form.com/docs
- https://react-hook-form.com/advanced-usage
- https://react-hook-form.com/docs/useform
- https://react-hook-form.com/docs/useform/subscribe
- https://react-hook-form.com/docs/useform/seterror
- https://react-hook-form.com/docs/useform/setvalue
- https://react-hook-form.com/docs/useform/resetdefaultvalues
- https://react-hook-form.com/docs/useform/formstate
- https://react-hook-form.com/docs/usewatch
- https://react-hook-form.com/docs/usecontroller
- https://react-hook-form.com/docs/usefieldarray
- https://react-hook-form.com/docs/useformstate
- https://github.com/react-hook-form/react-hook-form/releases
- https://github.com/react-hook-form/resolvers
- https://ui.shadcn.com/docs/components/form
Source Files
This document was compiled from individual reference files. For detailed editing or extension:
| File | Description |
|---|---|
| references/_sections.md | Category definitions and impact ordering |
| assets/templates/_template.md | Template for creating new rules |
| SKILL.md | Quick reference entry point |
| metadata.json | Version and reference URLs |