rilaykit
Workflow

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.

HookReturnsRe-renders when
useFlowStepIndex()numberStep changes
useFlowTransitioning()booleanTransition state changes
useFlowInitializing()booleanInit state changes
useFlowSubmitting()booleanSubmit state changes
useFlowData()Record<string, unknown>Any data changes
useStepData()Record<string, unknown>Current step data changes
useStepDataById(stepId)Record<string, unknown> | undefinedSpecific step data changes
useVisitedSteps()Set<string>Visited steps change
usePassedSteps()Set<string>Passed steps change
useSkippedSteps()Set<string>Skipped steps change
useIsStepVisited(stepId)booleanStep visit state changes
useIsStepPassed(stepId)booleanStep pass state changes
useIsStepSkipped(stepId)booleanStep 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.
  • fieldConditionsRecord<string, ConditionEvaluationResult> per field.
  • allStepConditionsRecord<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>;
}

On this page