Global Configuration
The FlowProvider component allows you to configure default settings for all flows in your application. Individual flows can override these defaults when needed, giving you centralized configuration with flexibility.
Why use FlowProvider?
Section titled “Why use FlowProvider?”Without FlowProvider, you configure each flow individually:
// ❌ Repetitive configuration<Flow flow={flow1} persister={persister} saveMode="always" /><Flow flow={flow2} persister={persister} saveMode="always" /><Flow flow={flow3} persister={persister} saveMode="always" />With FlowProvider, you configure once:
// ✅ Configure once, apply everywhere<FlowProvider config={{ persister, saveMode: "always" }}> <Flow flow={flow1} /> <Flow flow={flow2} /> <Flow flow={flow3} /></FlowProvider>Basic usage
Section titled “Basic usage”Wrap your app with FlowProvider at the root level:
import { FlowProvider } from "@useflow/react";import { createLocalStorageStore, createPersister } from "@useflow/react";
const store = createLocalStorageStore(localStorage);const persister = createPersister({ store });
export function App() { return ( <FlowProvider config={{ persister, saveMode: "navigation", saveDebounce: 300 }}> <YourApp /> </FlowProvider> );}Now all flows will use these defaults:
// Uses global persister, saveMode, and saveDebounce<Flow flow={myFlow} initialContext={{...}} />Configuration options
Section titled “Configuration options”Persistence
Section titled “Persistence”Configure default persistence for all flows:
import { createLocalStorageStore, createPersister } from "@useflow/react";
const store = createLocalStorageStore(localStorage, { prefix: "myapp" });
const persister = createPersister({ store, ttl: 7 * 24 * 60 * 60 * 1000, // 7 days onError: (error) => { console.error('Persistence error:', error); }});
<FlowProvider config={{ persister }}> <App /></FlowProvider>Save mode
Section titled “Save mode”Set the default save mode for all flows:
<FlowProvider config={{ persister, saveMode: "always" // "always" | "navigation" | "manual"}}> <App /></FlowProvider>| Mode | When State is Saved |
|---|---|
navigation | On next/skip/back calls (default) |
always | On every context update |
manual | Only when calling save() |
Save debounce
Section titled “Save debounce”Control debounce delay for save operations:
<FlowProvider config={{ persister, saveMode: "always", saveDebounce: 500 // Wait 500ms after last change before saving}}> <App /></FlowProvider>This prevents excessive writes when state changes rapidly (e.g., typing in input fields).
Error handling
Section titled “Error handling”Configure global error handler for persistence failures:
<FlowProvider config={{ persister, onPersistenceError: (error) => { // Log to error tracking Sentry.captureException(error);
// Show user feedback toast.error('Failed to save your progress. Please check your connection.');
// Track analytics analytics.track('persistence_error', { message: error.message }); }}}> <App /></FlowProvider>Lifecycle callbacks
Section titled “Lifecycle callbacks”Configure global callbacks for flow lifecycle events:
<FlowProvider config={{ callbacks: { onFlowStart: (event) => { console.log('Flow started:', event.flowId); analytics.track('flow_started', { flowId: event.flowId, variantId: event.variantId, instanceId: event.instanceId }); },
onFlowComplete: (event) => { console.log('Flow completed:', event.flowId); analytics.track('flow_completed', { flowId: event.flowId, context: event.context }); },
onStepTransition: (event) => { console.log(`${event.flowId}: ${event.from} → ${event.to}`); analytics.track('step_transition', { flowId: event.flowId, from: event.from, to: event.to, direction: event.direction // "forward" | "backward" }); } }}}> <App /></FlowProvider>Callback event types
Section titled “Callback event types”onFlowStart:
{ flowId: string; variantId?: string; instanceId?: string; context: FlowContext;}onFlowComplete:
{ flowId: string; variantId?: string; instanceId?: string; context: FlowContext;}onStepTransition:
{ flowId: string; variantId?: string; instanceId?: string; from: string; to: string; direction: "forward" | "backward"; oldContext: FlowContext; newContext: FlowContext;}Overriding defaults
Section titled “Overriding defaults”Individual flows can override global configuration:
<FlowProvider config={{ persister: globalPersister, saveMode: "navigation", saveDebounce: 300}}> {/* Uses global config */} <Flow flow={standardFlow} />
{/* Overrides saveMode */} <Flow flow={criticalFlow} saveMode="always" />
{/* Overrides persister (doesn't persist) */} <Flow flow={temporaryFlow} persister={undefined} />
{/* Overrides persister with custom one */} <Flow flow={secureFlow} persister={encryptedPersister} /></FlowProvider>Accessing global config
Section titled “Accessing global config”Use the useFlowConfig hook to access global configuration from anywhere:
import { useFlowConfig } from "@useflow/react";
export function CustomComponent() { const config = useFlowConfig();
// Check if persistence is enabled globally if (config?.persister) { return <div>Progress will be saved</div>; }
return <div>Progress won't be saved</div>;}Returns null if no FlowProvider is present.
Nested providers
Section titled “Nested providers”You can nest FlowProvider components to create different configuration zones:
<FlowProvider config={{ saveMode: "navigation", persister: regularPersister}}> {/* Regular flows use navigation mode */} <Dashboard />
{/* Critical flows use always mode */} <FlowProvider config={{ saveMode: "always", persister: encryptedPersister }}> <PaymentFlow /> <HealthRecordsFlow /> </FlowProvider></FlowProvider>Inner providers override outer ones.
Common patterns
Section titled “Common patterns”Development vs production
Section titled “Development vs production”Different configurations for different environments:
const isDev = import.meta.env.DEV;
const persister = createPersister({ store: createLocalStorageStore(localStorage), onSave: isDev ? (flowId, state) => { console.log('💾 Saved:', flowId, state); } : undefined, onError: isDev ? console.error : (error) => { Sentry.captureException(error); }});
<FlowProvider config={{ persister, saveDebounce: isDev ? 100 : 500 // Faster feedback in dev}}> <App /></FlowProvider>Feature flags
Section titled “Feature flags”Enable/disable features globally:
const { featureFlags } = useFeatureFlags();
<FlowProvider config={{ persister: featureFlags.persistence ? persister : undefined, callbacks: { onFlowComplete: featureFlags.analytics ? trackCompletion : undefined }}}> <App /></FlowProvider>Multi-tenant apps
Section titled “Multi-tenant apps”Different configuration per tenant:
function TenantApp({ tenantId }: { tenantId: string }) { const tenantConfig = useTenantConfig(tenantId);
const persister = createPersister({ store: createLocalStorageStore(localStorage, { prefix: `tenant-${tenantId}` }) });
return ( <FlowProvider config={{ persister, saveMode: tenantConfig.autoSave ? "always" : "navigation", callbacks: { onFlowComplete: (event) => { // Tenant-specific analytics trackForTenant(tenantId, 'flow_completed', event); } } }}> <TenantDashboard /> </FlowProvider> );}A/B testing
Section titled “A/B testing”Different flows for different user groups:
const { variant } = useABTest('flow-autosave');
<FlowProvider config={{ persister, saveMode: variant === 'A' ? "navigation" : "always", callbacks: { onFlowComplete: (event) => { trackABTest('flow-autosave', variant, { flowId: event.flowId, completed: true }); } }}}> <App /></FlowProvider>TypeScript
Section titled “TypeScript”The FlowProviderConfig type is fully typed:
import type { FlowProviderConfig } from "@useflow/react";
const config: FlowProviderConfig = { persister, saveMode: "always", saveDebounce: 500, onPersistenceError: (error) => { // error is typed as Error }, callbacks: { onFlowStart: (event) => { // event is fully typed event.flowId; // string event.variantId; // string | undefined event.context; // FlowContext } }};Best practices
Section titled “Best practices”1. Configure at app root
Section titled “1. Configure at app root”Place FlowProvider as high as possible in your component tree:
// ✅ Good: At app root<FlowProvider> <Router> <App /> </Router></FlowProvider>
// ❌ Bad: Deep in tree, some flows won't have access<Router> <App> <FlowProvider> <OnboardingFlow /> </FlowProvider> </App></Router>2. Single persister instance
Section titled “2. Single persister instance”Create persister once and reuse it:
// ✅ Good: Created onceconst persister = createPersister({ store });
<FlowProvider config={{ persister }}>
// ❌ Bad: Created on every render<FlowProvider config={{ persister: createPersister({ store }) // New instance each render!}}>3. Memoize callbacks
Section titled “3. Memoize callbacks”Memoize callback functions to prevent unnecessary re-renders:
const handleFlowComplete = useCallback((event) => { analytics.track('flow_completed', event);}, []);
<FlowProvider config={{ callbacks: { onFlowComplete: handleFlowComplete }}}>4. Error boundaries
Section titled “4. Error boundaries”Wrap FlowProvider with error boundary for production resilience:
<ErrorBoundary fallback={<ErrorPage />}> <FlowProvider config={{ persister, onPersistenceError }}> <App /> </FlowProvider></ErrorBoundary>5. Default to sensible defaults
Section titled “5. Default to sensible defaults”Use conservative defaults that work for most flows:
<FlowProvider config={{ saveMode: "navigation", // Safe default saveDebounce: 300, // Reasonable delay onPersistenceError: (error) => { // Always handle errors }}}>Then override for special cases:
<Flow flow={criticalFlow} saveMode="always" />Complete example
Section titled “Complete example”View the complete production-ready app configuration
import { FlowProvider } from "@useflow/react";import { createLocalStorageStore, createPersister } from "@useflow/react";import { toast } from "sonner";import * as Sentry from "@sentry/react";
// Create storeconst store = createLocalStorageStore(localStorage, { prefix: "myapp"});
// Create persister with advanced optionsconst persister = createPersister({ store, ttl: 30 * 24 * 60 * 60 * 1000, // 30 days validate: (state) => { // Only restore active flows return state.status === 'active'; }, onSave: (flowId, state) => { console.log(`Saved ${flowId} at step ${state.stepId}`); }, onRestore: (flowId, state) => { toast.success('Continuing where you left off!'); }, onError: (error) => { console.error('Storage error:', error); }});
export function App() { return ( <FlowProvider config={{ persister, saveMode: "navigation", saveDebounce: 300,
onPersistenceError: (error) => { Sentry.captureException(error); toast.error('Failed to save progress'); },
callbacks: { onFlowStart: ({ flowId, instanceId }) => { analytics.track('flow_started', { flowId, instanceId, timestamp: Date.now() }); },
onFlowComplete: ({ flowId, context }) => { analytics.track('flow_completed', { flowId, context, timestamp: Date.now() });
// Show completion message toast.success('All done! 🎉'); },
onStepTransition: ({ flowId, from, to, direction }) => { analytics.track('step_transition', { flowId, from, to, direction }); } } }}> <Routes> <Route path="/onboarding" element={<OnboardingFlow />} /> <Route path="/checkout" element={<CheckoutFlow />} /> <Route path="/survey" element={<SurveyFlow />} /> </Routes> </FlowProvider> );}import { Flow } from "@useflow/react";import { onboardingFlow } from "./flow";
export function OnboardingFlow() { // Uses global config automatically return ( <Flow flow={onboardingFlow} initialContext={{ name: "", email: "" }} > {({ renderStep }) => ( renderStep({ welcome: <WelcomeStep />, profile: <ProfileStep />, complete: <CompleteStep /> }) )} </Flow> );}Next steps
Section titled “Next steps”- Persistence - Learn about persistence configuration
- Callbacks - Deep dive into flow callbacks
- Testing - Test flows with global configuration
- TypeScript - Type-safe configuration