Skip to main content

useSequentStep

Hook for active step components.

Signature

function useSequentStep<TResult = unknown>(): {
advance: (nextStep: StepLoader, contextPatch?: unknown) => void;
retreat: () => void;
resolve: (value?: TResult) => void;
abort: (reason?: unknown) => void;
context: unknown;
};

StepLoader

type StepLoader = () =>
| ComponentType
| ReactElement
| { default: ComponentType }
| Promise<ComponentType | ReactElement | { default: ComponentType }>;

A step is always passed as a factory:

  • Sync factory — returns a component reference immediately
  • Element factory — returns JSX directly, useful when async work determines props
  • Async factory — returns a promise resolving to a component, JSX element, or { default: ComponentType } (matching import())

The library normalizes async factories into React.lazy components internally. You never need to call React.lazy yourself.

import Step1 from "./Step1";

// Sync
advance(() => Step1);

// Sync element
advance(() => <Step1 plan="pro" />);

// Async (dynamic import)
advance(() => import("./Step1"));

// Async element after data loading
advance(async () => <Step1 plan={await loadPlan()} />);

Responsibilities

  • Move forward with advance()
  • Go backward with retreat()
  • Finish with resolve()
  • Cancel with abort()
  • Read flow-scoped context

Example

function PaymentStep() {
const { advance, context } = useSequentStep();

return (
<button onClick={() => advance(() => ConfirmStep, { plan: context.plan })}>
Continue
</button>
);
}

advance() accepts any StepLoader, including factories that return JSX directly:

advance(async () => {
const quote = await loadQuote();
return <ConfirmStep quote={quote} />;
});
tip

useSequentStep() is only valid inside the active step subtree. Chrome and idle children should use useSequentContext().