Graneet Form LogoGraneet form

Wizard System

Multi-step form components and hooks for creating complex wizards

Wizard System

Graneet Form provides a comprehensive wizard system for creating multi-step forms with automatic state management, validation, and navigation between steps.

Core Components

useWizard Hook

The useWizard hook is the foundation of the wizard system. It manages step navigation, validation, and state persistence.

import { useWizard } from 'graneet-form';

type WizardValues = {
  userInfo: { name: string; email: string };
  preferences: { theme: 'light' | 'dark' };
};

const steps = [
  { name: 'userInfo' as const },
  { name: 'preferences' as const }
];

const wizard = useWizard<WizardValues>(
  steps,
  (wizardValues) => {
    // Called when wizard finishes
    console.log('Wizard completed:', wizardValues);
  },
  () => {
    // Called when wizard is quit from first step
    console.log('Wizard quit');
  }
);

WizardContext

Provides wizard state to child components throughout the wizard tree.

import { WizardContext, useWizardContext } from 'graneet-form';

function WizardComponent() {
  const wizard = useWizard(steps, onFinish, onQuit);

  return (
    <WizardContext.Provider value={wizard}>
      {/* Wizard steps */}
    </WizardContext.Provider>
  );
}

function NavigationButtons() {
  const { goNext, goPrevious, isFirstStep, isLastStep } = useWizardContext();
  
  return (
    <div>
      {!isFirstStep && <button onClick={goPrevious}>Previous</button>}
      <button onClick={goNext}>
        {isLastStep ? 'Finish' : 'Next'}
      </button>
    </div>
  );
}

Step Component

The Step component renders content only when it's the active step.

import { Step } from 'graneet-form';

function UserInfoStep() {
  return (
    <Step name="userInfo">
      <h2>User Information</h2>
      {/* Step content */}
    </Step>
  );
}

Form Integration

useStepForm Hook

Integrates forms within wizard steps with automatic persistence and validation synchronization.

import { useStepForm, Form, Field } from 'graneet-form';

type WizardValues = {
  userInfo: { name: string; email: string };
  preferences: { theme: 'light' | 'dark' };
};

function UserInfoStep() {
  const { form } = useStepForm<WizardValues, 'userInfo'>({
    defaultValues: { name: '', email: '' }
  });

  return (
    <Step name="userInfo">
      <Form form={form}>
        <Field name="name" />
        <Field name="email" />
      </Form>
    </Step>
  );
}

Key features of useStepForm:

  • Automatic persistence: Form values are saved when navigating between steps
  • Validation sync: Step validation status affects wizard navigation
  • Value restoration: Previously entered values are restored when returning to a step
  • Ready state management: Controls when the step allows navigation

useStepStatus Hook

Monitors the validation status of the current step.

import { useStepStatus } from 'graneet-form';

function StepNavigation() {
  const stepStatus = useStepStatus();
  const { goNext, isStepReady } = useWizardContext();

  return (
    <button 
      onClick={goNext} 
      disabled={!isStepReady || stepStatus !== 'valid'}
    >
      Next Step
    </button>
  );
}

Advanced Features

Step Validation

Add custom validation logic that runs before proceeding to the next step:

const steps = [
  {
    name: 'userInfo' as const,
    onNext: async (stepValues) => {
      // Custom validation logic
      if (!stepValues?.email?.includes('@')) {
        alert('Please enter a valid email');
        return false; // Prevent navigation
      }
      return true; // Allow navigation
    }
  }
];

The wizard context provides several navigation methods:

const {
  goNext,        // Move to next step or finish
  goPrevious,    // Move to previous step or quit
  goBackTo,      // Jump to a specific previous step
  currentStep,   // Current active step
  steps,         // Array of all step names
  isFirstStep,   // Boolean - is current step the first?
  isLastStep,    // Boolean - is current step the last?
  isStepReady    // Boolean - is step ready for navigation?
} = useWizardContext();

// Jump back to a specific step
const handleGoToUserInfo = () => {
  goBackTo('userInfo');
};

Accessing Step Values

Retrieve form values from any step:

const {
  getValuesOfStep,          // Get values from specific step
  getValuesOfCurrentStep,   // Get values from current step
  getValuesOfSteps         // Get all step values
} = useWizardContext();

// Get values from a specific step
const userInfo = getValuesOfStep('userInfo');

// Get all wizard values
const allValues = getValuesOfSteps();

Complete Example

import { 
  useWizard, 
  useStepForm, 
  WizardContext, 
  Step, 
  Form, 
  Field 
} from 'graneet-form';

type WizardValues = {
  userInfo: { name: string; email: string };
  preferences: { theme: 'light' | 'dark'; notifications: boolean };
};

const steps = [
  { name: 'userInfo' as const },
  { 
    name: 'preferences' as const,
    onNext: async (values) => {
      // Validate preferences step
      return values?.theme !== undefined;
    }
  }
];

function UserInfoStep() {
  const { form } = useStepForm<WizardValues, 'userInfo'>({
    defaultValues: { name: '', email: '' }
  });

  return (
    <Step name="userInfo">
      <h2>User Information</h2>
      <Form form={form}>
        <Field name="name" placeholder="Your name" />
        <Field name="email" type="email" placeholder="Your email" />
      </Form>
    </Step>
  );
}

function PreferencesStep() {
  const { form } = useStepForm<WizardValues, 'preferences'>({
    defaultValues: { theme: 'light', notifications: true }
  });

  return (
    <Step name="preferences">
      <h2>Preferences</h2>
      <Form form={form}>
        <Field name="theme" type="select">
          <option value="light">Light</option>
          <option value="dark">Dark</option>
        </Field>
        <Field name="notifications" type="checkbox" />
      </Form>
    </Step>
  );
}

function WizardNavigation() {
  const { 
    goNext, 
    goPrevious, 
    isFirstStep, 
    isLastStep, 
    isStepReady,
    currentStep 
  } = useWizardContext();

  return (
    <div>
      <p>Current step: {currentStep}</p>
      <div>
        {!isFirstStep && (
          <button onClick={goPrevious}>Previous</button>
        )}
        <button 
          onClick={goNext} 
          disabled={!isStepReady}
        >
          {isLastStep ? 'Finish' : 'Next'}
        </button>
      </div>
    </div>
  );
}

export function MyWizard() {
  const wizard = useWizard<WizardValues>(
    steps,
    (wizardValues) => {
      console.log('Wizard completed with values:', wizardValues);
      // Handle wizard completion
    },
    () => {
      console.log('Wizard quit');
      // Handle wizard quit
    }
  );

  return (
    <WizardContext.Provider value={wizard}>
      <div>
        <h1>Setup Wizard</h1>
        
        <UserInfoStep />
        <PreferencesStep />
        
        <WizardNavigation />
      </div>
    </WizardContext.Provider>
  );
}

Type Safety

The wizard system is fully typed to ensure type safety across steps:

type WizardValues = {
  step1: { field1: string };
  step2: { field2: number };
};

// TypeScript will enforce correct step names and value types
const { form } = useStepForm<WizardValues, 'step1'>(); // ✅ Valid
const { form } = useStepForm<WizardValues, 'invalidStep'>(); // ❌ Type error

// Step values are properly typed
const step1Values = getValuesOfStep('step1'); // Type: { field1: string } | undefined