# React Hook Form

> Build or refactor React forms with React Hook Form. Use when working with useForm, register, handleSubmit, formState errors, defaultValues, validation rules, Controller, useController, FormProvider, useFieldArray, Zod or other resolvers, server submissions, and TypeScript form values. For simple local input state without React Hook Form use react-use-state.

- Skill: `flpbalada/react-hook-form` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add flpbalada/react-hook-form`
- Raw SKILL.md: https://api.skillmd.com/api/skills/flpbalada/react-hook-form/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: flpbalada (https://skillmd.com/u/flpbalada)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/flpbalada/react-hook-form

---


# React Hook Form

## Goal

Use React Hook Form's uncontrolled-first model.
Prefer native inputs with `register`.
Use `Controller` only when a component cannot expose normal input props.

## Rules

- Use `useForm<FormValues>()` near the form boundary.
- Set `defaultValues` for every editable field.
- Use `register` for native inputs and ref-forwarding custom inputs.
- Use `Controller` for controlled third-party inputs.
- Use `useController` for reusable field components.
- Use `FormProvider` and `useFormContext` for large nested forms.
- Use `useFieldArray` for repeatable rows.
- Use a resolver for shared schema validation.
- Use `reset(newValues)` when loaded data becomes the clean baseline.
- Use `useWatch` only when one component needs another field value.
- Do not pass both `register` and `Controller` to one field.
- Do not mirror every input with local `useState`.

## Flow

1. Inspect existing form conventions.
2. Define or infer `FormValues`.
3. Add `useForm<FormValues>()` with defaults.
4. Register fields.
5. Add validation rules or resolver.
6. Render `formState.errors`.
7. Submit with `handleSubmit(onSubmit)`.

## Pattern

```tsx
type FormValues = { email: string };

const {
  register,
  handleSubmit,
  formState: { errors },
} = useForm<FormValues>({ defaultValues: { email: "" } });

return (
  <form onSubmit={handleSubmit(save)}>
    <input {...register("email", { required: "Email is required" })} />
    {errors.email?.message ? <p role="alert">{errors.email.message}</p> : null}
    <button type="submit">Save</button>
  </form>
);
```

## Controlled Inputs

Use `Controller` for MUI fields, React Select, date pickers, masked inputs, or custom controlled components.
Pass `field.value`, `field.onChange`, `field.onBlur`, and `field.ref`.
Use `fieldState` for field-level errors.

## Schema Validation

Prefer Zod or existing project schema tooling for complex forms.
Infer `FormValues` from the schema.
Use schema coercion or `valueAsNumber` for non-string values.

## Output

- Typed form values.
- Default values for each field.
- Field validation near rules or schema.
- User-safe validation messages.
- Clear submit path.

## Reference

Read `references/patterns.md` for controlled inputs, nested forms, repeatable fields, async defaults, resolvers, or typed reusable fields.

