← Back to Docs

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 useEffect cleanup.
  • Guard transitions — never advance from "done" without explicit user action.
  • Keep payload immutable; spread on update to avoid stale references.