Skip to main content

react-hook-form-zod

Type-safe React forms with React Hook Form and Zod validation. Use for form schemas, field arrays, multi-step forms, or encountering validation errors, resolver issues, nested field problems.

ソース情報

リポジトリ
secondsky/claude-skills
ソースの最終更新活動
2026年8月5日 23:44
検出された SKILL.md の言語
英語
スター
227
フォーク
32

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

ファイルエクスプローラー
19 ファイル

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
react-hook-form-zod
description
Type-safe React forms with React Hook Form and Zod validation. Use for form schemas, field arrays, multi-step forms, or encountering validation errors, resolver issues, nested field problems.
metadata
{"keywords":["react-hook-form","useForm","zod validation","zodResolver","@hookform/resolvers","form schema","register","handleSubmit","formState","useFieldArray","useWatch","useController","Controller","shadcn form","Field component","client server validation","nested validation","array field validation","dynamic fields","multi-step form","async validation","zod refine","z.infer","form error handling","uncontrolled to controlled","resolver not found","schema validation error"]}
license
MIT
# React Hook Form + Zod Validation **Status**: Production Ready ✅ **Last Updated**: 2025-11-21 **Dependencies**: None (standalone) **Latest Versions**: react-hook-form@7.84.0, zod@4.3.6, @hookform/resolvers@5.2.2 --- ## Quick Start (10 Minutes) ### 1. Install Packages ```bash bun add react-hook-form@7.84.0 zod@4.3.6 @hookform/resolvers@5.2.2 ``` **Why These Packages**: - **react-hook-form**: Performant, flexible forms with minimal re-renders - **zod**: TypeScript-first schema validation with type inference - **@hookform/resolvers**: Adapter connecting Zod to React Hook Form ### 2. Create Your First Form ```typescript import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' // 1. Define validation schema const loginSchema = z.object({ email: z.email({ error: 'Invalid email address' }), password: z.string().min(8, 'Password must be at least 8 characters'), }) // 2. Infer TypeScript type from schema type LoginFormData = z.infer<typeof loginSchema> function LoginForm() { // 3. Initialize form with zodResolver const { register, handleSubmit, formState: { errors, isSubmitting }, } = useForm<LoginFormData>({ resolver: zodResolver(loginSchema), defaultValues: { email: '', password: '', }, }) // 4. Handle form submission const onSubmit = async (data: LoginFormData) => { // Data is guaranteed to be valid here console.log('Valid data:', data) } return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <label htmlFor="email">Email</label> <input id="email" type="email" {...register('email')} /> {errors.email && ( <span role="alert" className="error"> {errors.email.message} </span> )} </div> <div> <label htmlFor="password">Password</label> <input id="password" type="password" {...register('password')} /> {errors.password && ( <span role="alert" className="error"> {errors.password.message} </span> )} </div> <button type="submit" disabled={isSubmitting}> {isSubmitting ? 'Logging in...' : 'Login'} </button> </form> ) } ``` **CRITICAL**: - Always set `defaultValues` to prevent "uncontrolled to controlled" warnings - Use `zodResolver(schema)` to connect Zod validation - Type form with `z.infer<typeof schema>` for full type safety - Validate on both client AND server (never trust client validation alone) **Template**: See `templates/basic-form.tsx` for complete working example ### 3. Add Server-Side Validation ```typescript // server/api/login.ts import { z } from 'zod' // SAME schema on server const loginSchema = z.object({ email: z.email({ error: 'Invalid email address' }), password: z.string().min(8, 'Password must be at least 8 characters'), }) export async function loginHandler(req: Request) { try { const data = loginSchema.parse(await req.json()) // Data is type-safe and validated return { success: true } } catch (error) { if (error instanceof z.ZodError) { return { success: false, errors: z.flattenError(error).fieldErrors } } throw error } } ``` **Why Server Validation**: - Client validation can be bypassed (inspect element, Postman, curl) - Server validation is your security layer - Same Zod schema = single source of truth **Template**: See `templates/server-validation.tsx` --- ## Core Concepts ### useForm Hook ```typescript const { register, // Register input fields handleSubmit, // Wrap onSubmit handler formState, // Form state (errors, isValid, isDirty, etc.) setValue, // Set field value programmatically getValues, // Get current form values watch, // Watch field values reset, // Reset form to defaults trigger, // Trigger validation manually control, // For Controller/useController } = useForm<FormData>({ resolver: zodResolver(schema), mode: 'onSubmit', // When to validate defaultValues: {}, // Initial values (REQUIRED) }) ``` **Validation Modes**: - `onSubmit` - Validate on submit (best performance) - `onChange` - Validate on every change (live feedback) - `onBlur` - Validate when field loses focus (good balance) - `all` - Validate on submit, blur, and change **Reference**: See `references/rhf-api-reference.md` for complete API ### Zod Schema Basics ```typescript import { z } from 'zod' // Basic types const schema = z.object({ email: z.email({ error: 'Invalid email' }), age: z.number().min(18, 'Must be 18+'), terms: z.boolean().refine(val => val === true, 'Must accept terms'), }) // Nested objects const addressSchema = z.object({ user: z.object({ name: z.string(), email: z.email(), }), address: z.object({ street: z.string(), city: z.string(), zip: z.string().regex(/^\d{5}$/), }), }) // Arrays const tagsSchema = z.object({ tags: z.array(z.string()).min(1, 'At least one tag required'), }) // Optional and nullable const optionalSchema = z.object({ middleName: z.string().optional(), nickname: z.string().nullable(), bio: z.string().nullish(), // optional AND nullable }) ``` **Reference**: See `references/zod-schemas-guide.md` for complete patterns --- ## Critical Rules ### Always Do ✅ **Always set `defaultValues`** - Prevents "uncontrolled to controlled" warnings ✅ **Use `zodResolver` for validation** - Connects Zod schemas to React Hook Form ✅ **Infer types from schema** - Use `z.infer<typeof schema>` for type safety ✅ **Validate on server too** - Client validation can be bypassed ✅ **Use `.register()` for native inputs** - Simple and performant ✅ **Use `Controller` for custom components** - For component libraries (MUI, Chakra, etc.) ✅ **Handle errors accessibly** - Use `role="alert"` for screen readers ✅ **Reset form after submission** - Use `reset()` to clear form state **Form Patterns**: See `templates/` for: - `basic-form.tsx` - Simple login/register forms - `advanced-form.tsx` - Nested objects, arrays, dynamic fields - `shadcn-form.tsx` - Integration with shadcn/ui - `multi-step-form.tsx` - Wizard/stepper forms - `async-validation.tsx` - Async field validation ### Never Do ❌ **Never skip `defaultValues`** - Causes "uncontrolled to controlled" errors ❌ **Never use only client validation** - Security vulnerability ❌ **Never mutate form values directly** - Use `setValue()` instead ❌ **Never ignore accessibility** - Always use proper labels and ARIA ❌ **Never forget to disable submit when `isSubmitting`** - Prevents double submissions **Performance**: See `references/performance-optimization.md` for: - When to use `mode: 'onBlur'` vs `'onChange'` - `useWatch` vs `watch()` - Re-render optimization strategies **Accessibility**: See `references/accessibility.md` for: - Proper label association - Error announcement - Focus management - Keyboard navigation --- ## Top 5 Critical Errors ### Error #1: Uncontrolled to Controlled Warning ⚠️ **Error:** ``` Warning: A component is changing an uncontrolled input to be controlled ``` **Cause**: Not setting `defaultValues` **Solution:** ```typescript // ❌ BAD const form = useForm() // ✅ GOOD const form = useForm({ defaultValues: { email: '', password: '', } }) ``` --- ### Error #2: Zod v4 Type Inference Issues **Error:** Type inference doesn't work correctly **Solution:** ```typescript // Explicitly type useForm if needed const form = useForm<z.infer<typeof schema>>({ resolver: zodResolver(schema), }) ``` **Source**: [GitHub Issue #13109](https://github.com/react-hook-form/react-hook-form/issues/13109) --- ### Error #3: Resolver Not Found **Error:** ``` Module not found: Can't resolve '@hookform/resolvers/zod' ``` **Solution:** ```bash # Install the resolvers package bun add @hookform/resolvers@5.2.2 ``` --- ### Error #4: Array Field Issues **Error:** Dynamic array fields not working with `useFieldArray` **Solution:** ```typescript const { fields, append, remove } = useFieldArray({ control, name: "items" // Must match schema field name exactly }) ``` **Template**: See `templates/dynamic-fields.tsx` --- ### Error #5: Custom Component Validation Fails **Error:** Third-party component (MUI, Chakra) doesn't validate **Solution:** Use `Controller` instead of `register`: ```typescript <Controller name="date" control={control} render={({ field }) => ( <DatePicker {...field} /> )} /> ``` **Reference**: See `references/error-handling.md` for all patterns --- **All 12 Errors**: See `references/top-errors.md` for complete documentation --- ## Common Patterns ### Basic Form ```typescript import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' const schema = z.object({ name: z.string().min(1, 'Name required'), email: z.email({ error: 'Invalid email' }), }) type FormData = z.infer<typeof schema> function MyForm() { const { register, handleSubmit, formState: { errors } } = useForm<FormData>({ resolver: zodResolver(schema), defaultValues: { name: '', email: '' } }) const onSubmit = (data: FormData) => console.log(data) return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('name')} /> {errors.name && <span>{errors.name.message}</span>} <button type="submit">Submit</button> </form> ) } ``` **Template**: See `templates/basic-form.tsx` --- ### Dynamic Fields (useFieldArray) ```typescript import { useForm, useFieldArray } from 'react-hook-form' const schema = z.object({ items: z.array( z.object({ name: z.string(), quantity: z.number().min(1) }) ).min(1, 'At least one item required') }) function DynamicForm() { const { control, handleSubmit } = useForm({ resolver: zodResolver(schema), defaultValues: { items: [{ name: '', quantity: 1 }] } }) const { fields, append, remove } = useFieldArray({ control, name: 'items' }) return ( <form> {fields.map((field, index) => ( <div key={field.id}>
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る