/** * Dynamic Form Fields Example - useFieldArray * * Demonstrates: * - useFieldArray for dynamic add/remove functionality * - Array validation with Zod * - Proper key usage (field.id, not index) * - Nested field error handling * - Add, remove, update, insert operations */ import { useForm, useFieldArray } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' // Schema for contact list const contactSchema = z.object({ name: z.string().min(1, 'Name is required'), email: z.string().email('Invalid email'), phone: z.string().regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number').optional(), isPrimary: z.boolean().optional(), }) const contactListSchema = z.object({ contacts: z.array(contactSchema) .min(1, 'At least one contact is required') .max(10, 'Maximum 10 contacts allowed'), }) type ContactListData = z.infer export function DynamicContactList() { const { register, control, handleSubmit, formState: { errors }, } = useForm({ resolver: zodResolver(contactListSchema), defaultValues: { contacts: [{ name: '', email: '', phone: '', isPrimary: false }], }, }) const { fields, append, remove, insert, update } = useFieldArray({ control, name: 'contacts', }) const onSubmit = (data: ContactListData) => { console.log('Contacts:', data) } return (

Contact List

{/* Array error (min/max length) */} {errors.contacts && !Array.isArray(errors.contacts) && (
{errors.contacts.message}
)}
{fields.map((field, index) => (

Contact {index + 1}

{/* Name */}
{errors.contacts?.[index]?.name && ( {errors.contacts[index]?.name?.message} )}
{/* Email */}
{errors.contacts?.[index]?.email && ( {errors.contacts[index]?.email?.message} )}
{/* Phone */}
{errors.contacts?.[index]?.phone && ( {errors.contacts[index]?.phone?.message} )}
{/* Primary Contact Checkbox */}
))}
{/* Add Contact Button */}
{/* Submit */}
) } /** * Advanced Example: Skills with Custom Add */ const skillSchema = z.object({ name: z.string().min(1, 'Skill name is required'), level: z.enum(['beginner', 'intermediate', 'advanced', 'expert']), yearsOfExperience: z.number().int().min(0).max(50), }) const skillsFormSchema = z.object({ skills: z.array(skillSchema).min(1, 'Add at least one skill'), }) type SkillsFormData = z.infer export function DynamicSkillsForm() { const { register, control, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(skillsFormSchema), defaultValues: { skills: [], }, }) const { fields, append, remove } = useFieldArray({ control, name: 'skills', }) // Preset skill templates const addPresetSkill = (skillName: string) => { append({ name: skillName, level: 'intermediate', yearsOfExperience: 1, }) } const onSubmit = (data: SkillsFormData) => { console.log('Skills:', data) } return (

Your Skills

{errors.skills && !Array.isArray(errors.skills) && (
{errors.skills.message}
)} {/* Preset Skills */}

Quick Add:

{['React', 'TypeScript', 'Node.js', 'Python', 'SQL'].map((skill) => ( ))}
{/* Skills List */}
{fields.map((field, index) => (
{errors.skills?.[index]?.name && ( {errors.skills[index]?.name?.message} )}
))}
{/* Custom Add */}
) }