/** * Advanced Form Example - User Profile with Nested Objects and Arrays * * Demonstrates: * - Nested object validation (address) * - Array field validation (skills) * - Conditional field validation * - Complex Zod schemas with refinements * - Type-safe nested error handling */ import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' // Define nested schemas const addressSchema = z.object({ street: z.string().min(1, 'Street is required'), city: z.string().min(1, 'City is required'), state: z.string().min(2, 'State must be at least 2 characters'), zipCode: z.string().regex(/^\d{5}(-\d{4})?$/, 'Invalid ZIP code format'), country: z.string().min(1, 'Country is required'), }) // Complex schema with nested objects and arrays const profileSchema = z.object({ // Basic fields firstName: z.string().min(2, 'First name must be at least 2 characters'), lastName: z.string().min(2, 'Last name must be at least 2 characters'), email: z.string().email('Invalid email address'), phone: z.string().regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number').optional(), // Nested object address: addressSchema, // Array of strings skills: z.array(z.string().min(1, 'Skill cannot be empty')) .min(1, 'At least one skill is required') .max(10, 'Maximum 10 skills allowed'), // Conditional fields isStudent: z.boolean(), school: z.string().optional(), graduationYear: z.number().int().min(1900).max(2100).optional(), // Enum experience: z.enum(['junior', 'mid', 'senior', 'lead'], { errorMap: () => ({ message: 'Please select experience level' }), }), // Number with constraints yearsOfExperience: z.number() .int('Must be a whole number') .min(0, 'Cannot be negative') .max(50, 'Must be 50 or less'), // Date availableFrom: z.date().optional(), // Boolean agreedToTerms: z.boolean().refine((val) => val === true, { message: 'You must agree to the terms and conditions', }), }) .refine((data) => { // Conditional validation: if isStudent is true, school is required if (data.isStudent && !data.school) { return false } return true }, { message: 'School is required for students', path: ['school'], }) .refine((data) => { // Experience level should match years of experience if (data.experience === 'senior' && data.yearsOfExperience < 5) { return false } return true }, { message: 'Senior level requires at least 5 years of experience', path: ['yearsOfExperience'], }) type ProfileFormData = z.infer export function AdvancedProfileForm() { const { register, handleSubmit, watch, formState: { errors, isSubmitting }, setValue, } = useForm({ resolver: zodResolver(profileSchema), defaultValues: { firstName: '', lastName: '', email: '', phone: '', address: { street: '', city: '', state: '', zipCode: '', country: 'USA', }, skills: [''], // Start with one empty skill isStudent: false, school: '', experience: 'junior', yearsOfExperience: 0, agreedToTerms: false, }, }) // Watch isStudent to conditionally show school field const isStudent = watch('isStudent') const onSubmit = async (data: ProfileFormData) => { console.log('Profile data:', data) // API call } return (

User Profile

{/* Basic Information */}

Basic Information

{errors.firstName && ( {errors.firstName.message} )}
{errors.lastName && ( {errors.lastName.message} )}
{errors.email && ( {errors.email.message} )}
{errors.phone && ( {errors.phone.message} )}
{/* Address (Nested Object) */}

Address

{errors.address?.street && ( {errors.address.street.message} )}
{errors.address?.city && ( {errors.address.city.message} )}
{errors.address?.state && ( {errors.address.state.message} )}
{errors.address?.zipCode && ( {errors.address.zipCode.message} )}
{errors.address?.country && ( {errors.address.country.message} )}
{/* Skills (Array - simplified for advanced-form, see dynamic-fields.tsx for full array handling) */}

Skills

Enter skills separated by commas (handled as string for simplicity in this example)

{errors.skills && ( {errors.skills.message || errors.skills[0]?.message} )}
{/* Experience */}

Experience

{errors.experience && ( {errors.experience.message} )}
{errors.yearsOfExperience && ( {errors.yearsOfExperience.message} )}
{/* Conditional Fields */}

Education

{/* Conditional field - only show if isStudent is true */} {isStudent && (
{errors.school && ( {errors.school.message} )}
)}
{/* Terms and Conditions */}
{errors.agreedToTerms && ( {errors.agreedToTerms.message} )}
{/* Submit Button */}
) }