Core Concepts
Learn the fundamental concepts that power Stage Flow.
Overview
Stage Flow is built around several core concepts that work together to provide a powerful state management solution:
- Stages: The different states your application can be in
- Transitions: How your application moves between stages
- Data: The information that flows through your application
- Engine: The core that manages everything
- Configuration: How you define your stage machine
- Lifecycle: The events that occur during stage changes
- StageRenderer: The component that automatically renders stages
- StageFlowProvider: The context provider that makes the engine available
Stages
Stages represent the different states your application can be in. Each stage has a unique name and can contain specific data and behavior.
type AppStage = 'loading' | 'form' | 'success' | 'error';
const config = {
initial: 'loading' as const,
stages: [
{
name: 'loading',
transitions: [{ target: 'form', event: 'ready' }]
},
{
name: 'form',
transitions: [
{ target: 'success', event: 'submit' },
{ target: 'error', event: 'fail' }
]
},
{
name: 'success',
transitions: [{ target: 'form', event: 'reset' }]
},
{
name: 'error',
transitions: [{ target: 'form', event: 'retry' }]
}
]
};
Stage Properties
Each stage can have the following properties:
- name: Unique identifier for the stage
- transitions: Rules for moving to other stages
- effect: Optional effect to apply during transitions
- data: Optional initial data for the stage
Transitions
Transitions define how your application moves from one stage to another. They are triggered by events and can include data transformation.
{
name: 'form',
transitions: [
{
target: 'success',
event: 'submit',
condition: (context) => context.data?.email && context.data?.password
},
{
target: 'error',
event: 'fail'
}
]
}
Transition Properties
- target: The stage to transition to
- event: The event that triggers the transition
- condition: Optional condition that must be true for the transition
- after: Optional duration in milliseconds for automatic transition
- middleware: Optional middleware specific to this transition
Data
Data flows through your application and can be modified at each stage. Data is type-safe and can be accessed throughout your application.
interface AppData {
email?: string;
password?: string;
user?: {
id: string;
name: string;
email: string;
};
error?: string;
}
// Data is automatically typed and validated
// Stage components receive data as props
function FormComponent({ data, send }) {
// Access current data
console.log(data?.email);
// Send data with events
send('submit', { email: 'user@example.com', password: 'secret' });
}
Engine
The StageFlowEngine is the core component that manages your stage machine. It handles transitions, data management, and lifecycle events.
import { StageFlowEngine } from '@stage-flow/core';
const engine = new StageFlowEngine(config, {
plugins: [loggingPlugin],
middleware: [validationMiddleware]
});
// Start the engine
await engine.start();
// Send events
await engine.send('submit', { email: 'user@example.com' });
// Get current state
console.log(engine.getCurrentStage()); // 'success'
console.log(engine.getCurrentData()); // { user: { email: 'user@example.com' } }
Engine Configuration
The engine can be configured with:
- plugins: Extend functionality (logging, persistence, analytics)
- middleware: Process data and events
StageFlowProvider
The StageFlowProvider is a React context provider that makes the engine available to all child components.
import { StageFlowProvider } from '@stage-flow/react';
function App() {
const engine = new StageFlowEngine(config);
return (
<StageFlowProvider engine={engine}>
<StageRenderer
stageComponents={{
loading: LoadingComponent,
form: FormComponent,
success: SuccessComponent,
error: ErrorComponent
}}
/>
</StageFlowProvider>
);
}
StageRenderer
The StageRenderer is the main component that automatically renders the appropriate stage component based on the current stage.
import { StageRenderer } from '@stage-flow/react';
function App() {
return (
<StageFlowProvider engine={engine}>
<StageRenderer
stageComponents={{
loading: LoadingComponent,
form: FormComponent,
success: SuccessComponent,
error: ErrorComponent
}}
// Optional props
effects={{
form: { type: 'fade', duration: 300 }
}}
fallbackComponent={DefaultComponent}
/>
</StageFlowProvider>
);
}
StageRenderer Props
- stageComponents: Object mapping stage names to React components
- effects: Optional animation effects for stage transitions
- fallbackComponent: Component to render for undefined stages
- engine: Optional engine prop (if not using context)
Stage Components
Stage components are React components that receive the current data and send function as props.
function FormComponent({ data, send }) {
const handleSubmit = (email: string, password: string) => {
send('submit', { email, password });
};
return (
<form onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
handleSubmit(
formData.get('email') as string,
formData.get('password') as string
);
}}>
<input name="email" type="email" defaultValue={data?.email} />
<input name="password" type="password" />
<button type="submit">Submit</button>
</form>
);
}
Stage Component Props
- data: Current stage data
- send: Function to send events to the engine
- currentStage: Current stage name (optional)
Configuration
Configuration defines your stage machine structure. It's type-safe and provides excellent developer experience.
import { StageFlowConfig } from '@stage-flow/core';
const config: StageFlowConfig<AppStage, AppData> = {
initial: 'loading',
stages: [
{
name: 'loading',
transitions: [{ target: 'form', event: 'ready' }]
},
{
name: 'form',
transitions: [
{ target: 'success', event: 'submit' },
{ target: 'error', event: 'fail' }
]
},
{
name: 'success',
transitions: [{ target: 'form', event: 'reset' }]
},
{
name: 'error',
transitions: [{ target: 'form', event: 'retry' }]
}
]
};
Configuration Properties
- initial: The starting stage
- stages: Array of stage definitions
- plugins: Global plugins for the engine
- middleware: Global middleware for the engine
- effects: Global effects for the engine
Lifecycle
Stage Flow provides a rich lifecycle system that allows you to hook into different stages of your application's execution.
Stage Lifecycle
const config = {
stages: [
{
name: 'form',
transitions: [
{ target: 'success', event: 'submit' },
{ target: 'error', event: 'fail' }
]
}
]
};
Engine Lifecycle
const engine = new StageFlowEngine(config);
// Subscribe to engine changes
const unsubscribe = engine.subscribe((stage, data) => {
console.log(`Current stage: ${stage}`, data);
});
// Start the engine
await engine.start();
// Send events
await engine.send('submit', { email: 'user@example.com' });
// Clean up subscription
unsubscribe();
Type Safety
Stage Flow provides comprehensive type safety throughout your application.
Type Definitions
// Define your types
type AppStage = 'loading' | 'form' | 'success' | 'error';
type AppEvent = 'ready' | 'submit' | 'fail' | 'reset' | 'retry';
interface AppData {
email?: string;
password?: string;
user?: User;
error?: string;
}
// Type-safe configuration
const config: StageFlowConfig<AppStage, AppData> = {
// TypeScript will ensure all stages are defined
initial: 'loading',
stages: [
// TypeScript will validate stage names
{ name: 'loading', transitions: [] },
{ name: 'form', transitions: [] },
{ name: 'success', transitions: [] },
{ name: 'error', transitions: [] }
]
};
Type Inference
Stage Flow automatically infers types from your configuration:
// Types are automatically inferred in stage components
function FormComponent({ data, send }: StageProps<AppStage, AppData>) {
// TypeScript knows the current stage
// TypeScript knows data has email and password
console.log(data?.email, data?.password);
// TypeScript validates event names
send('submit', { email: 'user@example.com' }); // ✅ Valid
send('invalid', {}); // ❌ TypeScript error
}
Error Handling
Stage Flow provides robust error handling at multiple levels.
Validation Errors
const config = {
stages: [
{
name: 'form',
transitions: [
{
target: 'success',
event: 'submit',
condition: (context) => {
if (!context.data?.email) {
throw new Error('Email is required');
}
return true;
}
}
]
}
]
};
Error Recovery
const engine = new StageFlowEngine(config);
try {
await engine.send('submit', {});
} catch (error) {
// Handle validation errors
console.error('Validation failed:', error.message);
// Transition to error stage
await engine.send('fail', { error: error.message });
}
Error Boundaries (React)
import { StageErrorBoundary } from '@stage-flow/react';
function App() {
return (
<StageErrorBoundary
fallback={(error) => (
<div>
<h2>Something went wrong</h2>
<p>{error.message}</p>
<button onClick={() => window.location.reload()}>
Reload
</button>
</div>
)}
>
<StageFlowProvider engine={engine}>
<StageRenderer stageComponents={stageComponents} />
</StageFlowProvider>
</StageErrorBoundary>
);
}
Performance
Stage Flow is optimized for performance with several built-in optimizations.
Minimal Re-renders
function FormComponent({ data, send }) {
// Only re-renders when data changes
return (
<form onSubmit={(e) => {
e.preventDefault();
send('submit', { email: 'user@example.com' });
}}>
<input type="email" defaultValue={data?.email} />
<button type="submit">Submit</button>
</form>
);
}
Lazy Loading
const config = {
stages: [
{
name: 'form',
transitions: [
{
target: 'success',
event: 'submit',
condition: async (context) => {
// Lazy load user data
const user = await fetchUser(context.data?.email);
context.data = { ...context.data, user };
return true;
}
}
]
}
]
};
Memory Management
const engine = new StageFlowEngine(config, {
plugins: [
new PersistencePlugin({
key: 'app-state',
storage: sessionStorage, // Use session storage for temporary data
serialize: (data) => JSON.stringify(data),
deserialize: (str) => JSON.parse(str)
})
]
});
Best Practices
1. Keep Stages Simple
// ✅ Good: Simple, focused stages
const config = {
stages: [
{ name: 'loading', transitions: [{ target: 'form', event: 'ready' }] },
{ name: 'form', transitions: [{ target: 'success', event: 'submit' }] },
{ name: 'success', transitions: [{ target: 'form', event: 'reset' }] }
]
};
// ❌ Avoid: Complex stages with many responsibilities
const config = {
stages: [
{
name: 'complex',
transitions: [
{ target: 'success', event: 'submit' },
{ target: 'error', event: 'fail' },
{ target: 'loading', event: 'retry' },
{ target: 'form', event: 'back' }
]
}
]
};
2. Use Type Safety
// ✅ Good: Define types explicitly
type AppStage = 'loading' | 'form' | 'success' | 'error';
type AppData = { email?: string; user?: User; error?: string };
const config: StageFlowConfig<AppStage, AppData> = {
// TypeScript will catch errors
};
// ❌ Avoid: Using any types
const config: any = {
// No type safety
};
3. Handle Errors Gracefully
// ✅ Good: Proper error handling
const config = {
stages: [
{
name: 'form',
transitions: [
{
target: 'success',
event: 'submit',
condition: (context) => {
if (!context.data?.email || !context.data?.password) {
throw new Error('Email and password are required');
}
return true;
}
},
{
target: 'error',
event: 'fail'
}
]
}
]
};
4. Use Effects for Side Effects
// ✅ Good: Use conditions for API calls
const config = {
stages: [
{
name: 'form',
transitions: [
{
target: 'loading',
event: 'submit',
condition: async (context) => {
const user = await login(context.data?.email, context.data?.password);
context.data = { ...context.data, user };
return true;
}
}
]
}
]
};
Timers
Stage Flow provides built-in timer functionality for automatic transitions. Timers allow stages to automatically advance to the next stage after a specified duration.
Automatic Transitions
Use the after
property in transitions to create automatic transitions:
const config = {
stages: [
{
name: 'loading',
transitions: [
{ target: 'success', event: 'complete' },
{ target: 'error', event: 'fail' },
{ target: 'error', after: 10000 } // Auto-timeout after 10 seconds
]
},
{
name: 'success',
transitions: [
{ target: 'form', event: 'reset' },
{ target: 'form', after: 5000 } // Auto-reset after 5 seconds
]
}
]
};
Timer Control
Stage Flow provides comprehensive timer control methods:
- pauseTimers(): Pause all timers for the current stage
- resumeTimers(): Resume paused timers for the current stage
- resetTimers(): Reset timers to their original duration
- getTimerRemainingTime(): Get remaining time in milliseconds
- areTimersPaused(): Check if timers are currently paused
Timer State Management
Timers are managed internally by the StageFlowEngine and provide real-time state information:
// Real-time timer monitoring in stage components
function LoadingComponent({ data, send, getTimerRemainingTime, areTimersPaused }) {
const [remaining, setRemaining] = React.useState(0);
const [paused, setPaused] = React.useState(false);
React.useEffect(() => {
const interval = setInterval(() => {
setRemaining(getTimerRemainingTime());
setPaused(areTimersPaused());
}, 100);
return () => clearInterval(interval);
}, [getTimerRemainingTime, areTimersPaused]);
return (
<div>
<p>Loading... {remaining}ms {paused ? '(Paused)' : ''}</p>
</div>
);
}
Timer Best Practices
- Use for Auto-advance: Configure timers for automatic progression through stages
- Provide User Control: Allow users to pause, resume, and reset timers
- Show Visual Feedback: Display remaining time and pause status
- Handle Edge Cases: Consider what happens when timers expire during user interaction
- Stage-specific Logic: Only apply timer controls to stages that need them
Next Steps
- Basic Usage - See basic usage patterns
- TypeScript Usage - Advanced TypeScript features
- React Integration - React-specific features
- Plugin System - Extend functionality with plugins
- Middleware - Add processing layers