Teaches how to design and model XState v5 state machines from scratch using a systematic process. Use when starting a new state machine, deciding what should be a state vs context, choosing between actions and actors, or structuring events and states for a feature.
Teaches how to design and model XState v5 state machines from scratch using a systematic process. Use when starting a new state machine, deciding what should be a state vs context, choosing between actions and actors, or structuring events and states for a feature.
XState v5 Machine Modeling
The Modeling Process
Follow these 5 steps to build a state machine from scratch:
Step 1: List Events
List all events your machine cares about — things that happen from the outside:
- User clicks "Submit"
- User changes input value
- API returns data
- Timer expires
- WebSocket message received
Think in sequences: User changes input → User submits form → API responds.
Step 2: List Tasks (Side Effects)
List everything your machine needs to do:
- Validate form fields
- Send data to API
- Show notification
- Subscribe to WebSocket
- Focus an input
Step 3: Divide Tasks into Actions vs Actors
Actions — fire-and-forget. Use when you do NOT care about the result:
Log analytics event
Update context
Focus an input
Show a toast notification
Actors — long-running or result-dependent. Use when you need to:
Wait for a response (Promise)
Handle success AND failure
Clean up on exit (subscriptions)
Communicate bidirectionally
Decision rule: "Do I need to react to the outcome?" → Yes = Actor, No = Action.
Step 4: Define the Initial State
Ask: "What is the machine doing before anything happens?" That's your initial state.
A form →
editing
A data fetcher → idle
An auth flow → unauthenticated
Step 5: Build States Iteratively
For each event, ask: "In which state can this happen, and where does it lead?"
Wildcard transitions: 'form.*' matches all form events
Self-documenting event hierarchy
Easy to filter in devtools
Keep payloads minimal — include only data needed to process the event.
State Naming
Name states by what the machine is doing, not what happened:
// GOOD: describes current activitystates: {
idle: {},
loading: {},
editing: {},
submitting: {},
validating: {},
}
// BAD: describes past eventstates: {
submitted: {}, // What is the machine doing NOW?loaded: {}, // Is it showing data? Waiting?
}
Exception: success and failure are acceptable terminal state names.
Anti-Patterns
Boolean Flags Instead of States
// BADcontext: { isLoading: false, isError: false, isSuccess: false }
// Can be isLoading AND isError — impossible states are possible!// GOODstates: { idle: {}, loading: {}, success: {}, error: {} }
// Only one at a time, guaranteed.