useFlowState Hook
The useFlowState hook provides access to the current flow state, context, and navigation methods from within step components. It can be used in two ways: with a flow-specific hook or the generic hook.
Flow-specific hook (recommended)
Section titled “Flow-specific hook (recommended)”Using the flow’s custom hook provides full type safety:
import { defineFlow } from "@useflow/react";
const myFlow = defineFlow({ id: "myFlow", start: "step1", steps: { step1: { next: "step2" }, step2: { next: "step3" }, step3: {} }});
function MyStep() { const { stepId, next, context } = myFlow.useFlowState({ step: "step1" }); // All properties are fully typed for this specific flow return <div>{stepId}</div>;}Generic hook
Section titled “Generic hook”For accessing flow state in shared components:
import { useFlowState } from "@useflow/react";
function SharedStepComponent() { const { stepId, context, next } = useFlowState<MyContextType>(); // Generic hook - requires context type parameter return <div>{stepId}</div>;}Return value
Section titled “Return value”The hook returns an object containing:
State properties
Section titled “State properties”{ // Current step stepId: string; step: StepInfo; // Current step configuration
// Flow context context: TContext;
// Flow status status: "active" | "completed";
// Navigation availability canGoBack: boolean; // True if user can navigate back canGoNext: boolean; // True if user can navigate forward
// Timestamps startedAt: number; completedAt?: number;
// Navigation tracking path: readonly PathEntry[]; history: readonly HistoryEntry[];}Navigation methods
Section titled “Navigation methods”{ // Move forward next: (target?: string, update?: ContextUpdate) => void; next: (update?: ContextUpdate) => void;
// Skip step skip: (target?: string, update?: ContextUpdate) => void; skip: (update?: ContextUpdate) => void;
// Go back back: () => void;
// Reset flow reset: () => void;
// Update context setContext: (update: ContextUpdate<TContext>) => void;
// Restore saved state restore: (state: FlowState) => void;}Metadata
Section titled “Metadata”{ // All steps in the flow steps: Record<string, StepInfo>;
// Possible next steps from current position nextSteps?: readonly string[];
// Persistence state isRestoring: boolean;
// Save state (when using persistence) save: () => Promise<void>;}Type definitions
Section titled “Type definitions”PathEntry
Section titled “PathEntry”interface PathEntry { stepId: string; startedAt: number;}HistoryEntry
Section titled “HistoryEntry”interface HistoryEntry { stepId: string; action: "next" | "skip" | "back"; startedAt: number; completedAt?: number;}ContextUpdate
Section titled “ContextUpdate”type ContextUpdate<TContext> = | Partial<TContext> // Object with partial updates | ((current: TContext) => TContext); // Updater functionStepInfo
Section titled “StepInfo”interface StepInfo<TNext = string> { next?: TNext | readonly TNext[];}Examples
Section titled “Examples”Basic navigation
Section titled “Basic navigation”function ProfileStep() { const { next, back, context } = myFlow.useFlowState({ step: "profile" });
return ( <div> <h2>Welcome, {context.name}!</h2> <button onClick={back}>Back</button> <button onClick={() => next()}>Continue</button> </div> );}Context updates
Section titled “Context updates”function FormStep() { const { next, setContext } = myFlow.useFlowState({ step: "form" }); const [name, setName] = useState("");
const handleSubmit = () => { // Update context and navigate next({ name });
// Or update context separately setContext({ name }); next(); };
return ( <form onSubmit={handleSubmit}> <input value={name} onChange={(e) => setName(e.target.value)} /> <button type="submit">Submit</button> </form> );}Conditional navigation
Section titled “Conditional navigation”function BranchStep() { const { next, context } = myFlow.useFlowState({ step: "branch" });
return ( <div> {/* Component-driven navigation */} <button onClick={() => next("option1")}> Choose Option 1 </button> <button onClick={() => next("option2")}> Choose Option 2 </button>
{/* Or context-driven (with resolver) */} <button onClick={() => next({ userType: "business" })}> Continue as Business </button> </div> );}Skip optional steps
Section titled “Skip optional steps”function OptionalStep() { const { next, skip } = myFlow.useFlowState({ step: "optional" });
return ( <div> <h2>Optional Configuration</h2> <button onClick={() => next({ configured: true })}> Configure </button> <button onClick={() => skip({ skipped: true })}> Skip for now </button> </div> );}Check navigation availability
Section titled “Check navigation availability”function NavigableStep() { const { next, back, canGoBack, canGoNext } = myFlow.useFlowState({ step: "middle" });
return ( <div> <button onClick={back} disabled={!canGoBack}> Back </button> <button onClick={() => next()} disabled={!canGoNext}> Next </button> </div> );}Access step configuration
Section titled “Access step configuration”function CurrentStep() { const { step, stepId, steps } = myFlow.useFlowState({ step: "current" });
// Current step config console.log("Current step:", stepId); console.log("Next steps:", step.next);
// All steps in flow console.log("Total steps:", Object.keys(steps).length);
return <div>Step {stepId}</div>;}Track progress
Section titled “Track progress”function ProgressStep() { const { path, history, startedAt } = myFlow.useFlowState({ step: "progress" });
// Linear progress const stepsCompleted = path.length - 1;
// Total actions taken (including back navigation) const totalActions = history.length;
// Time elapsed const timeElapsed = Date.now() - startedAt;
return ( <div> <p>Steps completed: {stepsCompleted}</p> <p>Total actions: {totalActions}</p> <p>Time: {Math.floor(timeElapsed / 1000)}s</p> </div> );}Manual state persistence
Section titled “Manual state persistence”function PersistableStep() { const { save, isRestoring } = myFlow.useFlowState({ step: "persistable" });
if (isRestoring) { return <div>Loading saved progress...</div>; }
const handleSave = async () => { await save(); alert("Progress saved!"); };
return ( <div> <button onClick={handleSave}>Save Progress</button> </div> );}Best practices
Section titled “Best practices”1. Use flow-specific hooks
Section titled “1. Use flow-specific hooks”Always prefer the flow-specific hook for better type safety:
// ✅ Good - full type safetyconst { next, context, stepId } = myFlow.useFlowState({ step: "myStep" });
// ❌ Avoid - requires manual typingconst { next, context, stepId } = useFlowState<MyContext>();2. Handle loading states
Section titled “2. Handle loading states”Check isRestoring when using persistence:
function MyStep() { const { isRestoring } = myFlow.useFlowState({ step: "myStep" });
if (isRestoring) { return <LoadingSpinner />; }
return <div>Ready!</div>;}3. Validate before navigation
Section titled “3. Validate before navigation”Always validate data before navigating:
function FormStep() { const { next } = myFlow.useFlowState({ step: "form" }); const [data, setData] = useState({});
const isValid = validateData(data);
return ( <button onClick={() => next(data)} disabled={!isValid} > Continue </button> );}4. Use semantic actions
Section titled “4. Use semantic actions”Choose the right navigation method:
// ✅ Good - semantic actionsnext({ data }); // User completed stepskip({ skipped: true }); // User chose to skipback(); // User went back
// ❌ Bad - unclear intentnext({ skipped: true }); // Confusing - is this a skip?See also
Section titled “See also”- Flow Component - The parent component that provides flow context
- defineFlow - Creating flow definitions
- Navigation Guide - Detailed navigation patterns
- Context Guide - Managing flow state