react-hook-form

React Hook Form with Zod

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-hook-form" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-react-hook-form

React Hook Form with Zod

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: react-hook-form for comprehensive documentation on React Hook Form integration with Zod and shadcn/ui.

Form Setup

import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod';

const loginSchema = z.object({ email: z.string().email('Invalid email'), password: z.string().min(8, 'Password must be at least 8 characters'), });

type LoginFormData = z.infer<typeof loginSchema>;

function LoginForm() { const form = useForm<LoginFormData>({ resolver: zodResolver(loginSchema), defaultValues: { email: '', password: '', }, });

const onSubmit = async (data: LoginFormData) => { await login(data); };

return ( <form onSubmit={form.handleSubmit(onSubmit)}> {/* fields */} </form> ); }

With shadcn/ui Form Components

import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button';

function LoginForm() { const form = useForm<LoginFormData>({ resolver: zodResolver(loginSchema), defaultValues: { email: '', password: '' }, });

return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input placeholder="email@example.com" {...field} /> </FormControl> <FormMessage /> </FormItem> )} />

    &#x3C;FormField
      control={form.control}
      name="password"
      render={({ field }) => (
        &#x3C;FormItem>
          &#x3C;FormLabel>Password&#x3C;/FormLabel>
          &#x3C;FormControl>
            &#x3C;Input type="password" {...field} />
          &#x3C;/FormControl>
          &#x3C;FormMessage />
        &#x3C;/FormItem>
      )}
    />

    &#x3C;Button type="submit" disabled={form.formState.isSubmitting}>
      {form.formState.isSubmitting ? 'Loading...' : 'Login'}
    &#x3C;/Button>
  &#x3C;/form>
&#x3C;/Form>

); }

Complex Validation Schema

const userSchema = z.object({ name: z.string() .min(2, 'Name must be at least 2 characters') .max(100, 'Name must be less than 100 characters'), email: z.string().email('Invalid email'), role: z.enum(['admin', 'user', 'manager'], { errorMap: () => ({ message: 'Select a valid role' }), }), department: z.string().optional(), startDate: z.date({ required_error: 'Start date is required', }), salary: z.number() .min(0, 'Salary must be positive') .optional(), });

// Conditional validation const formSchema = z.object({ hasAddress: z.boolean(), address: z.string().optional(), }).refine( (data) => !data.hasAddress || data.address, { message: 'Address is required', path: ['address'] } );

Select Field

import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';

<FormField control={form.control} name="role" render={({ field }) => ( <FormItem> <FormLabel>Role</FormLabel> <Select onValueChange={field.onChange} defaultValue={field.value}> <FormControl> <SelectTrigger> <SelectValue placeholder="Select role" /> </SelectTrigger> </FormControl> <SelectContent> <SelectItem value="admin">Admin</SelectItem> <SelectItem value="user">User</SelectItem> <SelectItem value="manager">Manager</SelectItem> </SelectContent> </Select> <FormMessage /> </FormItem> )} />

Date Picker Field

import { Calendar } from '@/components/ui/calendar'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { format } from 'date-fns'; import { CalendarIcon } from 'lucide-react';

<FormField control={form.control} name="startDate" render={({ field }) => ( <FormItem className="flex flex-col"> <FormLabel>Start Date</FormLabel> <Popover> <PopoverTrigger asChild> <FormControl> <Button variant="outline" className="w-full justify-start text-left"> <CalendarIcon className="mr-2 h-4 w-4" /> {field.value ? format(field.value, 'PPP') : 'Pick a date'} </Button> </FormControl> </PopoverTrigger> <PopoverContent className="w-auto p-0"> <Calendar mode="single" selected={field.value} onSelect={field.onChange} initialFocus /> </PopoverContent> </Popover> <FormMessage /> </FormItem> )} />

Form with API Mutation

import { useMutation, useQueryClient } from '@tanstack/react-query';

function UserForm({ userId }: { userId?: string }) { const queryClient = useQueryClient(); const isEditing = !!userId;

const mutation = useMutation({ mutationFn: isEditing ? usersApi.update : usersApi.create, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); toast.success(isEditing ? 'User updated' : 'User created'); }, onError: (error) => { toast.error(error.message); }, });

const onSubmit = (data: UserFormData) => { if (isEditing) { mutation.mutate({ id: userId, ...data }); } else { mutation.mutate(data); } };

return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)}> {/* fields */} <Button type="submit" disabled={mutation.isPending}> {mutation.isPending ? 'Saving...' : 'Save'} </Button> </form> </Form> ); }

Key Patterns

Pattern Description

zodResolver

Zod validation integration

form.formState

Form state (isSubmitting, errors, etc.)

form.reset()

Reset form to defaults

form.setValue()

Programmatic value setting

form.watch()

Watch field changes

form.trigger()

Trigger validation

When NOT to Use This Skill

  • React 19 Server Actions forms - Use react-19 skill for useActionState patterns

  • Basic form handling - Use react-forms skill for general form patterns

  • Non-shadcn/ui components - Adapt patterns or use react-forms skill

  • Non-Zod validation - Use react-forms skill for other validation libraries

Anti-Patterns

Anti-Pattern Problem Solution

Not using zodResolver Manual validation, boilerplate Always use zodResolver with Zod

Missing FormField wrapper No error handling, poor UX Wrap inputs in FormField

Not checking formState.isSubmitting Button not disabled during submit Use isSubmitting to disable button

Using register without FormField No shadcn/ui integration Use FormField with Controller

Not calling reset() after success Form not cleared Call form.reset() on successful submit

Complex validation in component Hard to test, maintain Define schema with Zod

Not handling mutation errors Silent failures Use onError in mutation

Quick Troubleshooting

Issue Likely Cause Fix

Validation not working Missing zodResolver Add resolver: zodResolver(schema) to useForm

Errors not displaying Missing FormMessage Add in FormField

Form not resetting Not calling reset() Call form.reset() after successful submit

Submit button not disabling Not using isSubmitting Use form.formState.isSubmitting

Custom component not working Not using Controller Wrap with Controller in FormField

Default values not showing Not in defaultValues Add to defaultValues in useForm

Type errors Schema not matching FormData type Ensure z.infer matches interface

Reference Documentation

  • Form Patterns

  • Validation Schemas

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

webrtc

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-19

No summary provided by upstream source.

Repository SourceNeeds Review