UNPKG

@matthew.ngo/reform

Version:

A flexible and powerful React form management library with advanced validation, state observation, and multi-group support

394 lines (335 loc) 16.9 kB
# Reform Form Wizard Reform provides a powerful form wizard system that allows you to create multi-step forms with navigation, validation, and state management. This document covers the form wizard capabilities of Reform. ## Table of Contents - [Overview](#overview) - [Basic Usage](#basic-usage) - [Step Configuration](#step-configuration) - [Navigation](#navigation) - [Validation](#validation) - [Persistence](#persistence) - [API Reference](#api-reference) ## Overview Reform's form wizard system allows you to: 1. **Create multi-step forms** with a clear navigation flow 2. **Validate each step** before proceeding to the next 3. **Track completion status** of steps 4. **Customize step behavior** with lifecycle hooks 5. **Persist wizard state** across sessions ## Basic Usage Here's a basic example of creating a multi-step form wizard: ```tsx import { useReform, useReformWizard } from '@reform/core'; type UserForm = { // Step 1: Personal Information firstName: string; lastName: string; email: string; // Step 2: Address street: string; city: string; state: string; zipCode: string; // Step 3: Account username: string; password: string; confirmPassword: string; }; const RegistrationForm = () => { // Initialize Reform form const form = useReform<UserForm>({ defaultData: { firstName: '', lastName: '', email: '', street: '', city: '', state: '', zipCode: '', username: '', password: '', confirmPassword: '', }, minGroups: 1, }); // Initialize wizard with steps const wizard = useReformWizard(form, { steps: [ { id: 'personal', title: 'Personal Information', groupIndices: [0], // Use the first form group fields: ['firstName', 'lastName', 'email'], }, { id: 'address', title: 'Address', groupIndices: [0], fields: ['street', 'city', 'state', 'zipCode'], }, { id: 'account', title: 'Account Setup', groupIndices: [0], fields: ['username', 'password', 'confirmPassword'], }, ], validateOnNext: true, }); // Handle form submission const handleSubmit = (data: any) => { console.log('Form submitted:', data); }; return ( <div className="wizard-container"> {/* Step indicator */} <div className="wizard-steps"> {wizard.getSteps().map(step => ( <div key={step.id} className={`wizard-step ${step.isActive ? 'active' : ''} ${ step.isCompleted ? 'completed' : '' } ${!step.isEnabled ? 'disabled' : ''}`} onClick={() => step.isEnabled && wizard.goToStep(step.id)} > {step.title} </div> ))} </div> {/* Current step content */} <form onSubmit={form.formMethods.handleSubmit(handleSubmit)}> {wizard.currentStep.id === 'personal' && ( <div className="step-content"> <h2>Personal Information</h2> <div> <label>First Name</label> <input {...form.register(0, 'firstName')} /> </div> <div> <label>Last Name</label> <input {...form.register(0, 'lastName')} /> </div> <div> <label>Email</label> <input {...form.register(0, 'email')} type="email" /> </div> </div> )} {wizard.currentStep.id === 'address' && ( <div className="step-content"> <h2>Address</h2> <div> <label>Street</label> <input {...form.register(0, 'street')} /> </div> <div> <label>City</label> <input {...form.register(0, 'city')} /> </div> <div> <label>State</label> <input {...form.register(0, 'state')} /> </div> <div> <label>Zip Code</label> <input {...form.register(0, 'zipCode')} /> </div> </div> )} {wizard.currentStep.id === 'account' && ( <div className="step-content"> <h2>Account Setup</h2> <div> <label>Username</label> <input {...form.register(0, 'username')} /> </div> <div> <label>Password</label> <input {...form.register(0, 'password')} type="password" /> </div> <div> <label>Confirm Password</label> <input {...form.register(0, 'confirmPassword')} type="password" /> </div> </div> )} {/* Navigation buttons */} <div className="wizard-navigation"> {wizard.canGoPrev && ( <button type="button" onClick={wizard.prev}> Previous </button> )} {wizard.canGoNext ? ( <button type="button" onClick={wizard.next}> Next </button> ) : ( <button type="submit">Submit</button> )} </div> </form> </div> ); }; ``` ## Step Configuration Each step in the wizard can be configured with various options: ```tsx const wizard = useReformWizard(form, { steps: [ { id: 'personal', title: 'Personal Information', groupIndices: [0], // Which form groups this step applies to // Optional: Custom validation for this step validate: groups => { const data = groups[0].data; if (!data.email.includes('@')) { form.setError(0, 'email', { type: 'manual', message: 'Invalid email format', }); return false; } return true; }, // Optional: Custom completion check isCompleted: groups => { const data = groups[0].data; return !!data.firstName && !!data.lastName && !!data.email; }, // Optional: Custom enabled check isEnabled: (allGroups, completedSteps) => { // This is the first step, so always enabled return true; }, // Optional: Lifecycle hooks onEnter: groups => { console.log('Entering personal information step'); // You can initialize data or perform side effects }, onLeave: (groups, direction) => { console.log(`Leaving personal information step (${direction})`); // Return false to prevent leaving the step if (direction === 'next' && !groups[0].data.email) { alert('Please enter your email before proceeding'); return false; } return true; }, }, // Other steps... ], }); ``` ## Navigation The wizard provides several methods for navigation: ```tsx // Go to the next step (with validation) wizard.next(); // Go to the previous step wizard.prev(); // Go to a specific step by ID wizard.goToStep('address'); // Go to a specific step by index wizard.goToStepByIndex(1); // Reset the wizard wizard.resetWizard({ resetFormData: true, // Also reset form data clearCompletedSteps: true, // Clear completed steps status }); ``` ## Validation You can control validation behavior with configuration options: ```tsx const wizard = useReformWizard(form, { steps: [...], // Validate the current step before proceeding to the next validateOnNext: true, // Allow skipping to any step (even if previous steps are not completed) allowSkipSteps: false, // Mark steps as completed when leaving them (if valid) markCompletedOnLeave: true, }); ``` ## Persistence The wizard can persist its state across sessions: ```tsx const wizard = useReformWizard(form, { steps: [...], // Enable state persistence persistState: true, // Custom storage key persistStateKey: 'my-registration-wizard', // Callbacks onStepCompleted: (stepId, groups) => { console.log(`Step ${stepId} completed`); analytics.trackStepCompletion(stepId); }, onWizardCompleted: (groups) => { console.log('Wizard completed'); analytics.trackWizardCompletion(); }, }); ``` ## API Reference ### useReformWizard ```typescript function useReformWizard<T extends Record<string, any>>( reform: ReformReturn<T>, config: FormWizardConfig<T> ): FormWizardReturn<T>; ``` #### FormWizardConfig | Option | Type | Default | Description | | -------------------- | -------------------------------------------------- | ----------------------- | --------------------------------------- | | steps | `FormStep<T>[]` | Required | Array of step configurations | | initialStepId | `string` | First step ID | ID of the initial step | | validateOnNext | `boolean` | `true` | Validate current step before proceeding | | allowSkipSteps | `boolean` | `false` | Allow skipping to any step | | markCompletedOnLeave | `boolean` | `true` | Mark steps as completed when leaving | | persistState | `boolean` | `false` | Persist wizard state to localStorage | | persistStateKey | `string` | `'reform-wizard-state'` | Storage key for persistence | | onStepCompleted | `(stepId: string, groups: FormGroup<T>[]) => void` | `undefined` | Callback when a step is completed | | onWizardCompleted | `(groups: FormGroup<T>[]) => void` | `undefined` | Callback when all steps are completed | #### FormStep | Property | Type | Description | | ------------ | -------------------------------------------------------------------------------------- | ------------------------------------------- | | id | `string` | Unique identifier for the step | | title | `string` | Display title for the step | | groupIndices | `number[]` | Indices of form groups this step applies to | | fields | `(keyof T)[]` | Optional list of fields in this step | | validate | `(groups: FormGroup<T>[]) => boolean \| Promise<boolean>` | Custom validation function | | isCompleted | `(groups: FormGroup<T>[]) => boolean` | Custom completion check | | isEnabled | `(allGroups: FormGroup<T>[], completedSteps: string[]) => boolean` | Custom enabled check | | onEnter | `(groups: FormGroup<T>[]) => void \| Promise<void>` | Called when entering the step | | onLeave | `(groups: FormGroup<T>[], direction: 'next' \| 'prev') => boolean \| Promise<boolean>` | Called when leaving the step | #### FormWizardReturn | Property/Method | Type | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------- | | steps | `FormStep<T>[]` | Array of step configurations | | currentStep | `FormStep<T>` | Current active step | | currentStepId | `string` | ID of current step | | completedSteps | `string[]` | IDs of completed steps | | isCompleted | `boolean` | Whether all steps are completed | | isValidating | `boolean` | Whether validation is in progress | | isTransitioning | `boolean` | Whether transitioning between steps | | isStepCompleted | `(stepId: string) => boolean` | Check if a step is completed | | isStepEnabled | `(stepId: string) => boolean` | Check if a step is enabled | | isCurrentStep | `(stepId: string) => boolean` | Check if a step is the current step | | markStepCompleted | `(stepId: string) => void` | Mark a step as completed | | markStepNotCompleted | `(stepId: string) => void` | Mark a step as not completed | | validateStep | `(stepId: string) => Promise<boolean>` | Validate a specific step | | goToStep | `(stepId: string) => Promise<boolean>` | Navigate to a specific step | | next | `() => Promise<boolean>` | Navigate to the next step | | prev | `() => Promise<boolean>` | Navigate to the previous step | | canGoNext | `boolean` | Whether can navigate to next step | | canGoPrev | `boolean` | Whether can navigate to previous step | | resetWizard | `(options?: { resetFormData?: boolean; clearCompletedSteps?: boolean }) => void` | Reset the wizard | | getSteps | `() => Array<FormStep<T> & { isActive: boolean; isCompleted: boolean; isEnabled: boolean }>` | Get all steps with status | | getStep | `(stepId: string) => (FormStep<T> & { isActive: boolean; isCompleted: boolean; isEnabled: boolean }) \| undefined` | Get a specific step with status | | getCurrentStep | `() => FormStep<T> & { isActive: boolean; isCompleted: boolean; isEnabled: boolean }` | Get current step with status | | getStepGroups | `(step: FormStep<T>) => FormGroup<T>[]` | Get form groups for a step | | getStepIndex | `(stepId: string) => number` | Get index of a step | | getCurrentStepIndex | `() => number` | Get index of current step | | goToStepByIndex | `(index: number) => Promise<boolean>` | Navigate to a step by index |