---
description: Form handling patterns with React Hook Form and Zod validation
globs: ["src/components/forms/**/*.tsx", "src/lib/validations/**/*.ts"]
alwaysApply: false
---

# Form Handling Rules

## React Hook Form + Zod Pattern

### Basic Form Setup
```tsx
// components/forms/UserForm.tsx
interface UserFormProps {
  onSubmit: (data: UserFormData) => void
  initialData?: Partial<User>
  isLoading?: boolean
}

export const UserForm = ({ onSubmit, initialData, isLoading }: UserFormProps) => {
  const form = useForm<UserFormData>({
    resolver: zodResolver(userSchema),
    defaultValues: {
      name: initialData?.name || '',
      email: initialData?.email || '',
      phone: initialData?.phone || '',
    }
  })

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Full Name</FormLabel>
              <FormControl>
                <Input placeholder="Enter full name" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input type="email" placeholder="Enter email" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        
        <Button type="submit" disabled={isLoading} className="w-full">
          {isLoading ? 'Saving...' : 'Save User'}
        </Button>
      </form>
    </Form>
  )
}
```

### Form Hook Pattern
```tsx
// hooks/useUserForm.ts
export const useUserForm = (userId?: string) => {
  const { data: user } = useUser(userId)
  const createUser = useCreateUser()
  const updateUser = useUpdateUser()
  
  const form = useForm<UserFormData>({
    resolver: zodResolver(userSchema),
    defaultValues: {
      name: '',
      email: '',
      phone: '',
    }
  })
  
  // Reset form when user data loads
  useEffect(() => {
    if (user) {
      form.reset({
        name: user.name,
        email: user.email,
        phone: user.phone || '',
      })
    }
  }, [user, form])
  
  const handleSubmit = (data: UserFormData) => {
    if (userId) {
      updateUser.mutate({ id: userId, data })
    } else {
      createUser.mutate(data)
    }
  }
  
  const isLoading = createUser.isPending || updateUser.isPending
  
  return {
    form,
    handleSubmit,
    isLoading,
    reset: form.reset,
  }
}
```

### Advanced Form Patterns

#### Multi-Step Form
```tsx
// hooks/useMultiStepForm.ts
export const useMultiStepForm = <T extends Record<string, any>>(
  steps: Array<{ key: string; schema: ZodSchema<any> }>,
  onComplete: (data: T) => void
) => {
  const [currentStep, setCurrentStep] = useState(0)
  const [formData, setFormData] = useState<Partial<T>>({})
  
  const currentStepConfig = steps[currentStep]
  
  const form = useForm({
    resolver: zodResolver(currentStepConfig.schema),
    defaultValues: formData[currentStepConfig.key] || {}
  })
  
  const nextStep = (data: any) => {
    setFormData(prev => ({ ...prev, [currentStepConfig.key]: data }))
    
    if (currentStep < steps.length - 1) {
      setCurrentStep(prev => prev + 1)
    } else {
      onComplete({ ...formData, [currentStepConfig.key]: data } as T)
    }
  }
  
  const prevStep = () => {
    if (currentStep > 0) {
      setCurrentStep(prev => prev - 1)
    }
  }
  
  return {
    form,
    currentStep,
    totalSteps: steps.length,
    isFirstStep: currentStep === 0,
    isLastStep: currentStep === steps.length - 1,
    nextStep,
    prevStep,
    handleSubmit: form.handleSubmit(nextStep)
  }
}
```

#### Dynamic Form Fields
```tsx
// components/forms/DynamicFieldArray.tsx
export const DynamicFieldArray = ({ name, control }: DynamicFieldArrayProps) => {
  const { fields, append, remove } = useFieldArray({
    control,
    name,
  })

  return (
    <div className="space-y-4">
      {fields.map((field, index) => (
        <div key={field.id} className="flex gap-2 items-end">
          <FormField
            control={control}
            name={`${name}.${index}.value`}
            render={({ field }) => (
              <FormItem className="flex-1">
                <FormLabel>Item {index + 1}</FormLabel>
                <FormControl>
                  <Input {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <Button
            type="button"
            variant="outline"
            size="icon"
            onClick={() => remove(index)}
          >
            <X className="h-4 w-4" />
          </Button>
        </div>
      ))}
      
      <Button
        type="button"
        variant="outline"
        onClick={() => append({ value: '' })}
      >
        <Plus className="h-4 w-4 mr-2" />
        Add Item
      </Button>
    </div>
  )
}
```

### Form Validation Rules

#### Complex Validation Schema
```tsx
// lib/validations/userProfile.ts
export const userProfileSchema = z.object({
  personal: z.object({
    firstName: z.string().min(2, 'First name required'),
    lastName: z.string().min(2, 'Last name required'),
    email: emailSchema,
    phone: phoneSchema.optional(),
    dateOfBirth: z.string().optional(),
  }),
  address: z.object({
    street: z.string().min(5, 'Street address required'),
    city: z.string().min(2, 'City required'),
    state: z.string().min(2, 'State required'),
    zipCode: z.string().regex(/^\d{5}(-\d{4})?$/, 'Invalid zip code'),
    country: z.string().min(2, 'Country required'),
  }),
  preferences: z.object({
    newsletter: z.boolean().default(false),
    notifications: z.boolean().default(true),
    theme: z.enum(['light', 'dark', 'system']).default('system'),
  })
})
```

#### Conditional Validation
```tsx
export const conditionalSchema = z.object({
  userType: z.enum(['individual', 'business']),
  email: emailSchema,
  companyName: z.string().optional(),
  taxId: z.string().optional(),
}).refine(
  (data) => {
    if (data.userType === 'business') {
      return data.companyName && data.taxId
    }
    return true
  },
  {
    message: "Company name and tax ID required for business accounts",
    path: ["companyName"],
  }
)
```

## Form Anti-Patterns
- Manual form state management
- Inline validation logic
- No error handling
- Uncontrolled components mixing with controlled
- No loading states during submission

## Form Best Practices
- Always use React Hook Form + Zod
- Extract form logic to custom hooks
- Provide loading states during submission
- Reset forms after successful submission
- Handle both client and server validation errors
