Balance Logo
Balance
Reckon Design System
Open playroom

Wizard

The Wizard package helps capture data in steps and provide ability to navigate between steps.
Install
pnpm add @balance-web/wizard
Import usage
import {
WizardProgress
} from '@balance-web/wizard';
  • Code
  • API

The Wizard package helps capture data in steps and provide ability to navigate between steps.

Usage

The snippet below demonstrates:

  • Create a wizard schema with 2 steps, each having their own state
  • Use useCreateWizard hook to create a wizard
  • Render a wizard nav using the WizardProgress component
  • Use the Wizard component and useWizard hook to render components for each step
// Shape of the data required by each step is defined by the consumer
type StepMetaData = { title: string, complete: boolean };
type Step1Type = StepMetaData & { step1State?: string };
type Step2Type = StepMetaData & { step2State?: string };
// Wizard schema is an object defined by the consumer with numbers as keys i.e Record<number, T>
// Valid { 1: { a : string } }. Invalid { step1: { a : string } }
type MyWizardSchema = {
1: Step1Type,
2: Step2Type,
};
const initialWizardState: MyWizardSchema = {
1: { title: 'Step 1', step1State: 'step 1 state', complete: false },
2: { title: 'Step 2', step2State: 'step 2 state', complete: false },
};
// This is used to retrieve the correct wizard from context, also useful for serialization.
const DEMO_WIZARD_ID = 'wizard-demo'
// Create a wizard object with steps, which holds the state for each step. This object returns functions that allows us to manipulate the wizard state.
// Initial state is recorded only once, use the functions return by this hook to manipulate state after that.
const wizard = useCreateWizard({
id: DEMO_WIZARD_ID,
initialState: initialWizardState,
initialStep: 1,
});
const { stepsState, setWizardState, currentStep, setCurrentStep } = wizard
<Stack gap="small">
<WizardProgress
steps={{
1: {
title: stepsState[1].title,
subtitle: 'Subtitle',
action: {
label: 'Reset',
action: () => {
setWizardState(() => initialWizardState);
setCurrentStep(1);
},
},
complete: stepsState[1].complete
},
2: { title: stepsState[2].title, complete: stepsState[2].complete },
}}
currentStep={currentStep}
/>
{/* WizardProvider is a convenience component that provides wizard state to its children. */}
<WizardProvider wizard={wizard}>
<Step1 />
<Step2 />
</WizardProvider>
</Stack>;
const Step1 = () => {
{/* Returns an instance of the wizard from context based on the id provided. Must be used in children of WizardProvider component. */}
const wizard = useWizard<MyWizardSchema>(DEMO_WIZARD_ID);
if (!wizard || wizard.currentStep !== 1) return null;
const { stepsState, nextStep } = wizard;
const state = stepsState[1];
return (
<div>
<div>{state.step1State}</div>
<button onClick={nextStep}>Next</button>
</div>
);
};
const Step2 = () => {
const wizard = useWizard<MyWizardSchema>(DEMO_WIZARD_ID);
if (!wizard || wizard.currentStep !== 2) return null;
const { stepsState, previousStep } = wizard;
const state = stepsState[2];
return (
<div>
<div>{state.step2State}</div>
<button onClick={previousStep}>Previous</button>
</div>
);
};

Resume from previous state

Below example demonstrates a flow where user has to leave the page to retrieve data from a 3rd party and then resume from where they left.

  • User clicks on something to leave the page.
  • You serialize the wizard state into local storage.
  • User does stuff 3rd party stuff and returns to the page.
  • You retrieve the wizard state from local storage using the wizard id and pass it as the initialState to the useCreateWizard hook.

Above example will be modified as:

// Grab wizard from local storage if possible
const wizardFromLocalStorage = localStorage.getItem(`wizard:${DEMO_WIZARD_ID}`);
// When resuming a wizard you want to make sure the data is in sync with the step you're on
// This function should evaluate the wizard state and determine which step to resume from
const initalStep = calculateCurrentStepFromWizardState(wizardFromLocalStorage);
const wizard = useCreateWizard({
id: DEMO_WIZARD_ID,
// If there's anything in your local storage it will use that as a starting point, otherwise it'll start from the beginning
initialState: wizardFromLocalStorage || initialWizardState,
initialStep: initalStep,
});
const Step2 = () => {
const wizard = useWizard < MyWizardSchema > DEMO_WIZARD_ID;
if (!wizard || wizard.currentStep !== 2) return null;
const { stepsState } = wizard;
const state = stepsState[2];
const goToThirdParty = () => {
// We serialize the wizard state before leaving the page
// Note that we only have to serialize the stepsState
localStorage.setItem(`wizard:${DEMO_WIZARD_ID}`, stepsState);
window.location.href = 'http://some3rdparty.com';
};
return (
<div>
<div>{state.step2State}</div>
<button onClick={goToThirdParty}>Leave page</button>
</div>
);
};
Copyright © 2024 Reckon. Designed and developed in partnership with Thinkmill.
Bitbucket logoJira software logoConfluence logo