react-hook-form-zod

Build type-safe validated forms in React using React Hook Form and Zod schema validation. Single schema works on both client and server for DRY validation with full TypeScript type inference via z.infer. Use when: building forms with validation, integrating shadcn/ui Form components, implementing mu

By ovachiever · 714 installs

npx skills add ovachiever/droid-tings --skill react-hook-form-zod

Source repository · Upstream listing

React Hook Form + Zod Validation Status : Production Ready ✅ Last Updated : 2025 11 20 Dependencies : None (standalone) Latest Versions : react hook form@7.66.1, zod@4.1.12, @hookform/resolvers@5.2.2 Quick Start (10 Minutes) 1. Install Packages Why These Packages : react hook form : Performant, flexible form library with minimal re renders zod : TypeScript first schema validation with type inference @hookform/resolvers : Adapter to connect Zod (and other validators) to React Hook Form 2. Create Your First 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) 3. Add Server Side Validation 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 Type safety across frontend and backend Core Concepts useForm Hook Anatomy useForm Options : Option Description Default resolver Validation resolver (e.g., zodResolver) undefined mode When to validate ('onSubmit', 'onChange', 'onBlur', 'all') 'onSubmit' reValidateMode When to re validate after error 'onChange' defaultValues Initial form values {} shouldUnregister Unregister inputs when unmounted false criteriaMode Return all errors or first error only 'firstError' Form Validation Modes : onSubmit Validate on submit (best performance, less responsive) onChange Validate on every change (live feedback, more re renders) onBlur Validate when field loses focus (good balance) all Validate on submit, blur, and change (most responsive, highest cost) Zod Schema Definition Type Inference : Zod Refinements (Custom Validation) Zod Transforms (Data Manipulation) zodResolver Integration What zodResolver Does : 1. Takes your Zod schema 2. Converts it to a format React Hook Form understands 3. Provides validation function that runs on form submission 4. Maps Zod errors to React Hook Form error format 5. Preserves type safety with TypeScript inference zodResolver Options : Form Registration Patterns Pattern 1: Simple Input Registration What register() Returns : Pattern 2: Controller (for Custom Components) Use Controller when the input doesn't expose ref (like custom components, React Select, date pickers, etc.): When to Use Controller : ✅ Third party UI libraries (React Select, Material UI, Ant Design, etc.) ✅ Custom components that don't expose ref ✅ Components that don't use onChange (like checkboxes with custom handlers) ✅ Need fine grained control over field behavior When NOT to Use Controller : ❌ Standard HTML inputs (use register instead it's simpler and faster) ❌ When performance is critical (Controller adds minimal overhead) Pattern 3: useController (Reusable Controlled Inputs) Error Handling Displaying Errors Error Object Structure Form Level Validation Errors Server Errors Integration Advanced Patterns Dynamic Form Fields (useFieldArray) useFieldArray API : fields Array of field items with unique IDs append(value) Add new item to end prepend(value) Add new item to beginning insert(index, value) Insert item at index remove(index) Remove item at index update(index, value) Update item at index replace(values) Replace entire array Async Validation with Debouncing Multi Step Form (Wizard) Conditional Validation shadcn/ui Integration Using Form Component (Legacy) Note : shadcn/ui states "We are not actively developing the Form component anymore." They recommend using the Field component for new implementations. Using Field Component (Recommended) Check shadcn/ui documentation for the latest Field component API as it's the actively maintained approach. Performance Optimization Form Mode Strategies Controlled vs Uncontrolled Inputs Recommendation : Use register for standard inputs, Controller only when necessary (third party components, custom behavior). Isolation with Controller shouldUnregister Flag When to use : ✅ Multi step forms where steps have different fields ✅ Conditional fields that should not persist ✅ Want to clear data when component unmounts When NOT to use : ❌ Want to preserve form data when toggling visibility ❌ Navigating between form sections (tabs, accordions) Accessibility Best Practices ARIA Attributes Error Announcements Focus Management Critical Rules Always Do ✅ Set defaultValues to prevent "uncontrolled to controlled" warnings ✅ Use zodResolver for Zod integration ✅ Type forms with z.infer ✅ Validate on both client AND server ✅ Use formState.errors for error display ✅ Add ARIA attributes for accessibility ✅ Use field.id for useFieldArray keys ✅ Debounce async validation Never Do ❌ Skip server side validation (security vulnerability!) ❌ Use Zod v4 without checking type inference ❌ Forget to spread {...field} in Controller ❌ Mutate form values directly ❌ Use inline validation without debouncing ❌ Mix controlled and uncontrolled inputs ❌ Use index as key in useFieldArray ❌ Forget defaultValues for all fields Known Issues Prevention This skill prevents 12 documented issues: Issue 1: Zod v4 Type Inference Errors Error : Type inference doesn't work correctly with Zod v4 Source : [GitHub Issue 13109](https://github.com/react hook form/react hook form/issues/13109) (Closed 2025 11 01) Why It Happens : Zod v4 changed how types are inferred Prevention : Use correct type patterns: type FormData = z.infer<typeof schema Note : Resolved in react hook form v7.66.x+. Upgrade to latest version to avoid this issue. Issue 2: Uncontrolled to Controlled Warning Error : "A component is changing an uncontrolled input to be controlled" Source : React documentation Why It Happens : Not setting defaultValues causes undefined value transition Prevention : Always set defaultValues for all fields Issue 3: Nested Object Validation Errors Error : Errors for nested fields don't display correctly Source : Common React Hook Form issue Why It Happens : Accessing nested errors incorrectly Prevention : Use optional chaining: errors.address?.street?.message Issue 4: Array Field Re renders Error : Form re renders excessively with array fields Source : Performance issue Why It Happens : Not using field.id as key Prevention : Use key={field.id} in useFieldArray map Issue 5: Async Validation Race Conditions Error : Multiple validation requests cause conflicting results Source : Common async pattern issue Why It Happens : No debouncing or request cancellation Prevention : Debounce validation and cancel pending requests Issue 6: Server Error Mapping Error : Server validation errors don't map to form fields Source : Integration issue Why It Happens : Server error format doesn't match React Hook Form format Prevention : Use setError() to map server errors to fields Issue 7: Default Values Not Applied Error : Form fields don't show default values Source : Common mistake Why It Happens : defaultValues set after form initialization Prevention : Set defaultValues in useForm options, not useState Issue 8: Controller Field Not Updating Error : Custom component doesn't update when value changes Source : Common Controller issue Why It Happens : Not spreading {...field} in render function Prevention : Always spread {...field} to custom component Issue 9: useFieldArray Key Warnings Error : React warning about duplicate keys in list Source : React list rendering Why It Happens : Using array index as key instead of field.id Prevention : Use field.id: key={field.id} Issue 10: Schema Refinement Error Paths Error : Custom validation errors appear at wrong field Source : Zod refinement behavior Why It Happens : Not specifying path in refinement options Prevention : Add path option: refine(..., { message: '...', path: ['fieldName'] }) Issue 11: Transform vs Preprocess Confusion Error : Data transformation doesn't work as expected Source : Zod API confusion Why It Happens : Using wrong method for use case Prevention : Use transform for output transformation, preprocess for input transformation Issue 12: Multiple Resolver Conflicts Error : Form validation doesn't work with multiple resolvers Source : Configuration error Why It Happens : Trying to use multiple validation libraries Prevention : Use single resolver (zodResolver), combine schemas if needed Templates See the templates/ directory for working examples: 1. basic form.tsx Simple login/signup form 2. advanced form.tsx Nested objects, arrays, conditional fields 3. shadcn form.tsx shadcn/ui Form component integration 4. server validation.ts Server side validation with same schema 5. async validation.tsx Async validation with debouncing 6. dynamic fields.tsx useFieldArray for adding/removing items 7. multi step form.tsx Wizard with per step validation 8. custom error display.tsx Custom error formatting 9. package.json Complete dependencies References See the references/ directory for deep dive documentation: 1. zod schemas guide.md Comprehensive Zod schema patterns 2. rhf api reference.md Complete React Hook Form API 3. error handling.md Error messages, formatting, accessibility 4. accessibility.md WCAG compliance, ARIA attributes 5. performance optimization.md Form modes, validation strategies 6. shadcn integration.md shadcn/ui Form vs Field components 7. top errors.md 12 common errors with solutions 8. links to official docs.md Organized documentation links Official Documentation React Hook Form : https://react hook form.com/ Zod : https://zod.dev/ @hookform/resolvers : https://github.com/react hook form/resolvers shadcn/ui Form : https://ui.shadcn.com/docs/components/form License : MIT Last Verified : 2025 11 20 Maintainer : Jeremy Dawes (jeremy@jezweb.net)