Multi-Step Wizard
A Stepper-driven multi-step form where state lives in a single reducer so step transitions preserve inputs.
Multi-step wizard
Section titled “Multi-step wizard”Stepper on the left, form sections on the right. State lives in a single
reducer so step transitions preserve inputs.
import { useReducer } from 'react';import { Button, Form, FormControl, FormField, FormLabel, Heading, Input, Stack, Stepper, StepperItem, StepperLabel, StepperTrigger,} from '@arshad-shah/cynosure-react';
type State = { step: number; values: { name?: string; email?: string; team?: string };};
type Action = | { type: 'next' } | { type: 'back' } | { type: 'goto'; step: number } | { type: 'set'; values: Partial<State['values']> };
function reducer(s: State, a: Action): State { switch (a.type) { case 'next': return { ...s, step: Math.min(s.step + 1, 2) }; case 'back': return { ...s, step: Math.max(s.step - 1, 0) }; case 'goto': return { ...s, step: a.step }; case 'set': return { ...s, values: { ...s.values, ...a.values } }; }}
export function Wizard() { const [state, dispatch] = useReducer(reducer, { step: 0, values: {}, });
const submit = async () => { await fetch('/api/signup', { method: 'POST', body: JSON.stringify(state.values), }); };
return ( <Stack direction={{ base: 'column', md: 'row' }} gap="6"> <Stepper orientation="vertical" value={state.step}> <StepperItem value={0}> <StepperTrigger onClick={() => dispatch({ type: 'goto', step: 0 })}> <StepperLabel>Your details</StepperLabel> </StepperTrigger> </StepperItem> <StepperItem value={1}> <StepperTrigger onClick={() => dispatch({ type: 'goto', step: 1 })}> <StepperLabel>Team</StepperLabel> </StepperTrigger> </StepperItem> <StepperItem value={2}> <StepperTrigger onClick={() => dispatch({ type: 'goto', step: 2 })}> <StepperLabel>Confirm</StepperLabel> </StepperTrigger> </StepperItem> </Stepper>
<Form onSubmit={(e) => { e.preventDefault(); if (state.step < 2) { dispatch({ type: 'next' }); } else { submit(); } }} > <Stack gap="4" flex="1"> {state.step === 0 && ( <> <Heading level={2}>Your details</Heading> <FormField name="name"> <FormLabel>Full name</FormLabel> <FormControl> <Input defaultValue={state.values.name} onBlur={(e) => dispatch({ type: 'set', values: { name: e.target.value } }) } /> </FormControl> </FormField> <FormField name="email"> <FormLabel>Email</FormLabel> <FormControl> <Input type="email" defaultValue={state.values.email} onBlur={(e) => dispatch({ type: 'set', values: { email: e.target.value } }) } /> </FormControl> </FormField> </> )}
{state.step === 1 && ( <> <Heading level={2}>Team</Heading> <FormField name="team"> <FormLabel>Team name</FormLabel> <FormControl> <Input defaultValue={state.values.team} onBlur={(e) => dispatch({ type: 'set', values: { team: e.target.value } }) } /> </FormControl> </FormField> </> )}
{state.step === 2 && ( <> <Heading level={2}>Confirm</Heading> <pre>{JSON.stringify(state.values, null, 2)}</pre> </> )}
<Stack direction="row" gap="3" justify="space-between"> <Button type="button" variant="outline" onClick={() => dispatch({ type: 'back' })} disabled={state.step === 0} > Back </Button> <Button type="submit"> {state.step < 2 ? 'Continue' : 'Create account'} </Button> </Stack> </Stack> </Form> </Stack> );}