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에서 보기