useStepLoading saved progress…

useStep

Build a custom React hook that drives a multi-step flow — a checkout wizard, an onboarding tour, a survey split across pages. useStep(maxStep) owns a single 1-indexed step number and hands back that number plus a bundle of helpers to move through the flow: go forward, go back, jump to a specific step, and reset. The interesting part isn't counting; it's keeping the step bounded — it can never run past the last step or below the first — and making the controls stack correctly when several fire in one event.

Signature

function useStep(maxStep: number): [
  currentStep: number,
  helpers: {
    goToNextStep: () => void;
    goToPrevStep: () => void;
    reset: () => void;
    canGoToNextStep: boolean;
    canGoToPrevStep: boolean;
    setStep: (step: number) => void;
  },
];

currentStep starts at 1. Steps are 1-indexed and run from 1 to maxStep inclusive. The hook returns a tuple — a currentStep value and a helpers object — so a component can name them however it likes.

Examples

function Wizard() {
  const [step, { goToNextStep, goToPrevStep, canGoToNextStep, canGoToPrevStep }] =
    useStep(3);

  return (
    <div>
      <p>Step {step} of 3</p>
      <button onClick={goToPrevStep} disabled={!canGoToPrevStep}>Back</button>
      <button onClick={goToNextStep} disabled={!canGoToNextStep}>Next</button>
    </div>
  );
}
// renders "Step 1 of 3"; Back is disabled. Next -> "Step 2 of 3";
// Next -> "Step 3 of 3" and Next becomes disabled.
// Steps are clamped to [1, maxStep]. Moving past an edge is a no-op.
const [step, { goToNextStep, goToPrevStep, setStep }] = useStep(3);
goToPrevStep();  // already at 1 -> stays 1
goToNextStep();  // 1 -> 2
goToNextStep();  // 2 -> 3
goToNextStep();  // already at 3 (maxStep) -> stays 3
setStep(99);     // out of range -> ignored, stays 3
setStep(2);      // in range -> 2

Notes

  • Steps are 1-indexed. The flow starts at 1, not 0, and the last valid step is maxStep.
  • Moving past an edge is a no-op. goToNextStep at maxStep leaves the step unchanged; goToPrevStep at 1 stays at 1. Nothing throws.
  • setStep ignores out-of-range values. A jump to n only takes effect when 1 <= n <= maxStep; anything outside that range is silently ignored, leaving the current step in place.
  • canGoToNextStep and canGoToPrevStep are booleans. canGoToNextStep is true when currentStep + 1 <= maxStep; canGoToPrevStep is true when currentStep - 1 >= 1. They are values, not functions — read them directly.
  • Controls must survive batched updates. React groups several helper calls in one event into a single re-render, so two goToNextStep calls in one handler should advance by two, not one.
  • You don't need to memoize the helpers for this version — returning fresh functions each render is acceptable. Stabilizing their identity is a separate exercise.
Loading editor…