/** * Multi-Step Form Example (Wizard) * * Demonstrates: * - Multi-step form with per-step validation * - Progress tracking * - Step navigation (next, previous) * - Partial schema validation * - Combined schema for final submission * - Preserving form state across steps */ import { useState } from 'react' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' // Step 1: Personal Information const step1Schema = z.object({ 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'), }) // Step 2: Address const step2Schema = 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'), }) // Step 3: Account const step3Schema = z.object({ username: z.string() .min(3, 'Username must be at least 3 characters') .regex(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores'), password: z.string() .min(8, 'Password must be at least 8 characters') .regex(/[A-Z]/, 'Password must contain uppercase letter') .regex(/[0-9]/, 'Password must contain number'), confirmPassword: z.string(), }).refine((data) => data.password === data.confirmPassword, { message: "Passwords don't match", path: ['confirmPassword'], }) // Combined schema for final validation const fullFormSchema = step1Schema.merge(step2Schema).merge(step3Schema) type FormData = z.infer type Step1Data = z.infer type Step2Data = z.infer type Step3Data = z.infer const TOTAL_STEPS = 3 export function MultiStepRegistrationForm() { const [currentStep, setCurrentStep] = useState(1) const { register, handleSubmit, trigger, getValues, formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(fullFormSchema), mode: 'onChange', // Validate as user types defaultValues: { firstName: '', lastName: '', email: '', phone: '', street: '', city: '', state: '', zipCode: '', username: '', password: '', confirmPassword: '', }, }) // Navigate to next step const nextStep = async () => { let fieldsToValidate: (keyof FormData)[] = [] if (currentStep === 1) { fieldsToValidate = ['firstName', 'lastName', 'email', 'phone'] } else if (currentStep === 2) { fieldsToValidate = ['street', 'city', 'state', 'zipCode'] } // Trigger validation for current step fields const isValid = await trigger(fieldsToValidate) if (isValid) { setCurrentStep((prev) => Math.min(prev + 1, TOTAL_STEPS)) } } // Navigate to previous step const prevStep = () => { setCurrentStep((prev) => Math.max(prev - 1, 1)) } // Final form submission const onSubmit = async (data: FormData) => { console.log('Complete form data:', data) // Make API call alert('Form submitted successfully!') } // Calculate progress percentage const progressPercentage = (currentStep / TOTAL_STEPS) * 100 return (
{/* Progress Bar */}
Step {currentStep} of {TOTAL_STEPS} {Math.round(progressPercentage)}% Complete
{/* Step Indicators */}
{[1, 2, 3].map((step) => (
{step < currentStep ? '✓' : step}
{step < TOTAL_STEPS && (
)}
))}
{/* Step 1: Personal Information */} {currentStep === 1 && (

Personal Information

{errors.firstName && ( {errors.firstName.message} )}
{errors.lastName && ( {errors.lastName.message} )}
{errors.email && ( {errors.email.message} )}
{errors.phone && ( {errors.phone.message} )}
)} {/* Step 2: Address */} {currentStep === 2 && (

Address

{errors.street && ( {errors.street.message} )}
{errors.city && ( {errors.city.message} )}
{errors.state && ( {errors.state.message} )}
{errors.zipCode && ( {errors.zipCode.message} )}
)} {/* Step 3: Account */} {currentStep === 3 && (

Create Account

{errors.username && ( {errors.username.message} )}
{errors.password && ( {errors.password.message} )}
{errors.confirmPassword && ( {errors.confirmPassword.message} )}
{/* Summary */}

Review Your Information:

Name: {getValues('firstName')} {getValues('lastName')}

Email: {getValues('email')}

Phone: {getValues('phone')}

Address: {getValues('street')}, {getValues('city')}, {getValues('state')} {getValues('zipCode')}

Username: {getValues('username')}

)} {/* Navigation Buttons */}
{currentStep < TOTAL_STEPS ? ( ) : ( )}
) }