Recipe: Multi-turn dialog state management
Pattern for managing complex, multi-step dialog flows without external state libraries.
Problem
Dialogs that span multiple turns — wizards, confirmation chains, progressive disclosure — quickly become unwieldy with boolean flags. You need typed state, transitions, and predictable reset behavior.
Core Pattern
type DialogStep = "idle" | "confirm" | "execute" | "done";
function useDialogState() {
const [step, setStep] = useState<DialogStep>("idle");
const [payload, setPayload] = useState(null);
const transition = (next: DialogStep, data?: any) => {
setStep(next);
if (data !== undefined) setPayload(data);
};
const reset = () => {
setStep("idle");
setPayload(null);
};
return { step, payload, transition, reset };
}Usage
Render dialog content conditionally on step. Each step calls transition to advance. Call reset on close or cancel to return to idle.
{step === "confirm" && (
<ConfirmPanel
data={payload}
onApprove={() => transition("execute")}
onCancel={reset}
/>
)}Edge Cases
- Always reset on dialog unmount via
useEffectcleanup. - Guard transitions — never advance from "done" without explicit user action.
- Keep payload immutable; spread on update to avoid stale references.