Write and refactor React forms using react-hook-form with Zod validation. Use when creating new form components, converting existing forms to react-hook-form, or implementing form validation patterns.
التثبيت
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
Write and refactor React forms using react-hook-form with Zod validation. Use when creating new form components, converting existing forms to react-hook-form, or implementing form validation patterns.
React Hook Form Writer
This skill helps you write new forms and refactor existing forms to use react-hook-form following project best practices.
When to Use
Creating new form components from scratch
Converting existing forms to react-hook-form
Adding validation to forms
Implementing complex form patterns (nested forms, field arrays, multi-step)
Core Principles
1. Always Use Zod for Validation
Define schemas with Zod and integrate via zodResolver:
import { z } from"zod";
import { zodResolver } from"@hookform/resolvers/zod";
const formSchema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email address"),
age: z.number().min(18, "Must be at least 18"),
});
typeFormValues = z.infer<typeof formSchema>;
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
email: "",
age: 18,
},
});
2. Prefer useController Over Controller
Use useController hook for better composability in custom field components:
Leverage react-hook-form's uncontrolled approach for native inputs:
// Good: Uncontrolled with register
<input {...register("name")} />
// Only use Controller/useController for third-party controlled components// (e.g., shadcn Select, custom date pickers, rich text editors)
4. Use field.onChange for User Interactions, setValue for Programmatic Updates
When working with useController, use field.onChange for user interactions:
// Good: field.onChange for user interactionsconst { field } = useController({ name: "status", control });
<SelectonValueChange={field.onChange}value={field.value}>
{options.map((opt) => (
<SelectItemkey={opt.value}value={opt.value}>
{opt.label}
</SelectItem>
))}
</Select>// Bad: setValue for user interactions (breaks controller lifecycle)<SelectonValueChange={(v) => setValue("status", v)} value={watch("status")}>
Use setValue (from useFormContext) for programmatic updates in effects:
CRITICAL: Never use field.onChange inside useEffect dependencies
useController returns new field objects on every render. Including them in useEffect dependencies while also calling field.onChange() inside the effect causes infinite loops:
// BAD: Infinite loop - field objects change every renderconst { field } = useController({ name: "status" });
useEffect(() => {
field.onChange(defaultValue); // Triggers re-render
}, [field, defaultValue]); // field changes → effect runs → onChange → re-render → repeat// GOOD: Use setValue (stable) for programmatic updates in effectsconst { setValue } = useFormContext();
const { field } = useController({ name: "status" });
useEffect(() => {
setValue("status", defaultValue); // setValue is stable
}, [defaultValue, setValue]);
// User interactions still use field.onChangeconsthandleSelect = (value: string) => {
field.onChange(value);
};
Summary:
field.onChange → user interaction handlers (onClick, onSelect, etc.)
setValue → programmatic updates in useEffect or callbacks based on external data
5. Always Provide Default Values
Always provide defaultValues in useForm for all fields: