Workflow Hooks
Zustand-powered hooks for granular workflow state access and optimal performance.
Workflow state lives in a Zustand store; granular selector hooks re-render a component only when the slice it reads changes. All hooks import from @rilaykit/workflow/react (or rilaykit/react).
import { useFlow, useFlowStepIndex, useFlowActions } from '@rilaykit/workflow/react';Context Hook
useFlow() returns the full workflow context. Convenient for top-level orchestration, but it subscribes to everything — it re-renders on every state change.
import { useFlow } from '@rilaykit/workflow/react';
function WorkflowOrchestrator() {
const workflow = useFlow();
return (
<div>
<p>Step {workflow.workflowState.currentStepIndex + 1} of {workflow.context.totalSteps}</p>
<button onClick={() => workflow.goNext()}>Next</button>
</div>
);
}Prefer the granular hooks below for leaf components. Reserve useFlow() for top-level layouts that already depend on many state slices.
Return type
interface WorkflowContextValue {
workflowState: {
currentStepIndex: number;
allData: Record<string, unknown>;
stepData: Record<string, unknown>;
visitedSteps: Set<string>;
passedSteps: Set<string>;
skippedSteps: Set<string>;
isSubmitting: boolean;
isTransitioning: boolean;
isInitializing: boolean;
};
workflowConfig: WorkflowConfig;
currentStep: StepConfig;
context: WorkflowContext;
formConfig?: FormConfiguration;
conditionsHelpers: UseWorkflowConditionsReturn;
currentStepMetadata?: Record<string, unknown>;
// Navigation
goToStep(stepIndex: number): Promise<boolean>;
goNext(): Promise<boolean>;
goPrevious(): Promise<boolean>;
skipStep(): Promise<boolean>;
canGoToStep(stepIndex: number): boolean;
canGoNext(): boolean;
canGoPrevious(): boolean;
canSkipCurrentStep(): boolean;
// Data
setValue(fieldId: string, value: unknown): void;
setStepData(data: Record<string, unknown>): void;
resetWorkflow(): void;
// Submission
submitWorkflow(): Promise<void>;
isSubmitting: boolean;
canSubmit: boolean;
// Persistence
persistNow?: () => Promise<void>;
isPersisting?: boolean;
persistenceError?: Error | null;
}Granular State Hooks
Each hook subscribes to a single store slice.
| Hook | Returns | Re-renders when |
|---|---|---|
useFlowStepIndex() | number | Step changes |
useFlowTransitioning() | boolean | Transition state changes |
useFlowInitializing() | boolean | Init state changes |
useFlowSubmitting() | boolean | Submit state changes |
useFlowData() | Record<string, unknown> | Any data changes |
useStepData() | Record<string, unknown> | Current step data changes |
useStepDataById(stepId) | Record<string, unknown> | undefined | Specific step data changes |
useVisitedSteps() | Set<string> | Visited steps change |
usePassedSteps() | Set<string> | Passed steps change |
useSkippedSteps() | Set<string> | Skipped steps change |
useIsStepVisited(stepId) | boolean | Step visit state changes |
useIsStepPassed(stepId) | boolean | Step pass state changes |
useIsStepSkipped(stepId) | boolean | Step skip state changes |
useFlowNavigationState() | { currentStepIndex, isTransitioning, isSubmitting } | Navigation state changes |
useFlowSubmitState() | { isSubmitting, isTransitioning, isInitializing } | Submit-related state changes |
import { useFlowStepIndex, useIsStepPassed } from '@rilaykit/workflow/react';
function StepIndicator({ stepId, stepIndex }: { stepId: string; stepIndex: number }) {
const currentIndex = useFlowStepIndex();
const isPassed = useIsStepPassed(stepId);
const isCurrent = currentIndex === stepIndex;
return (
<div className={isCurrent ? 'step-active' : isPassed ? 'step-passed' : 'step-pending'}>
Step {stepIndex + 1}
</div>
);
}Action Hook
useFlowActions() returns the store mutation functions. It subscribes to nothing — calling components never re-render on store changes.
setCurrentStep(index)— set the active step by index.setStepData(data, stepId)— replace a step's data.setAllData(data)— replace the entire workflow data object.setFieldValue(fieldId, value, stepId)— set a single field value in a step.setSubmitting(bool)/setTransitioning(bool)/setInitializing(bool)— toggle flags.markStepVisited(stepId)/markStepPassed(stepId)/markStepSkipped(stepId)— mark lifecycle state.reset()— reset the store to its initial state.loadPersistedState(state)— hydrate from a persisted state.
import { useFlowActions } from '@rilaykit/workflow/react';
function AdminResetButton() {
const { reset } = useFlowActions();
return <button onClick={reset}>Reset Workflow</button>;
}Low-level API
These are raw store mutations. For navigation and data operations, prefer the higher-level methods from useFlow() (goNext(), setValue(), …), which handle validation, transitions, and side effects. useFlowStoreApi() exposes the raw store itself — but writing via store.setState bypasses data normalization; always write through useFlowActions().
Step Metadata Hook
useStepMetadata() reads step-level metadata from your workflow configuration — useful for conditional rendering driven by arbitrary metadata.
current— metadata of the current step.getByStepId(stepId)/getByStepIndex(index)— metadata of a specific step.hasCurrentKey(key)— whether the current step's metadata contains a key.getCurrentValue<T>(key, defaultValue?)— typed value from the current step's metadata.getAllStepsMetadata()— metadata for all steps.findStepsByMetadata(predicate)— steps whose metadata matches a predicate.
import { useStepMetadata } from '@rilaykit/workflow/react';
function StepLayout({ children }: { children: React.ReactNode }) {
const metadata = useStepMetadata();
const showSidebar = metadata.getCurrentValue<boolean>('showSidebar', false);
const helpText = metadata.getCurrentValue<string>('helpText');
return (
<div className="step-layout">
<main>{children}</main>
{showSidebar && <aside>{helpText && <p>{helpText}</p>}</aside>}
</div>
);
}Condition Hooks
useWorkflowConditions({ workflowConfig, workflowState, currentStep }) evaluates step and field conditions for the entire workflow.
Return value:
stepConditions—{ visible: boolean; skippable: boolean }for the current step.fieldConditions—Record<string, ConditionEvaluationResult>per field.allStepConditions—Record<number, StepConditionResult>keyed by step index.- Helpers:
isStepVisible(index),isStepSkippable(index),isFieldVisible(fieldId),isFieldDisabled(fieldId),isFieldRequired(fieldId),isFieldReadonly(fieldId).
Inside the provider tree, the same helpers are already available as conditionsHelpers on useFlow() — call useWorkflowConditions() directly only outside it.
import { useFlow } from '@rilaykit/workflow/react';
function ConditionalField({ fieldId, children }: { fieldId: string; children: React.ReactNode }) {
const { conditionsHelpers } = useFlow();
if (!conditionsHelpers.isFieldVisible(fieldId)) return null;
return <div data-readonly={conditionsHelpers.isFieldReadonly(fieldId)}>{children}</div>;
}