| name | xstate |
| description | Expert guidance for XState state machines, actor model patterns, React integration, and statecharts. Use when implementing state machines, managing complex application state, using the actor model for concurrent state management, integrating XState with React components, or debugging machines. |
XState Skill
Expert guidance for using XState state machines and the actor model in JavaScript, TypeScript, and React projects.
When to Use This Skill
Invoke this skill when:
- Implementing state machines or statecharts
- Managing complex application state with XState
- Using the actor model for concurrent state management
- Integrating XState with React components
- Debugging or visualizing state machines
Core Principles
- Model behavior as finite state machines
- Use explicit transitions over implicit state changes
- Leverage guards for conditional transitions
- Use actions for side effects
- Keep machines pure and testable
- Keep context serializable - avoid storing object instances (improves Stately Inspector compatibility)
- Store only metadata in context, create disposable instances on-demand in invoke input functions
Machine Patterns
Basic Machine Structure
Context Design:
- Store metadata and primitives (numbers, strings, enums, simple objects)
- Avoid storing object instances (better serialization and debugging)
- Keep context serializable for Stately Inspector compatibility
State Hierarchy
Final States Pattern:
Nested states with sibling final state for sequential flows. Avoids brittle #rootMachine.childState targeting across scope boundaries:
ParentState: {
initial: 'Step1',
states: {
Step1: {
on: { 'Next step': 'Step2' }
},
Step2: {
on: { 'Complete': 'Done' }
},
Done: {
type: 'final'
}
},
onDone: {
target: 'NextParentState'
}
}
Actions and Side Effects
Parameterized Actions: Parameterized actions for reusable logic with different values. Params can be dynamic (functions) and help TypeScript type event properties:
actions: {
updatePosition: assign((_, params: { x: number; y: number }) => ({
position: { x: params.x, y: params.y }
}))
}
// Static params
entry: { type: 'updatePosition', params: { x: 100, y: 200 } }
// Dynamic params from event (TypeScript now knows event shape)
on: {
'Click': {
actions: {
type: 'updatePosition',
params: ({ event }) => ({ x: event.clientX, y: event.clientY })
}
}
}
Entry Actions for Preparation: Entry actions prepare state before async operations:
Moving: {
entry: 'calculateMetadata', // Prepare before invoke
invoke: {
src: 'animationActor',
input: ({ context }) => ({
// Use prepared metadata
duration: context.calculatedDuration
})
}
}
Guards and Conditions
Parameterized Guards: Parameterized guards for reusable conditional logic. Params can be dynamic (functions) and help TypeScript type event properties:
guards: {
isWithinBounds: (_, params: { x: number; y: number; bounds: Rect }) =>
params.x >= params.bounds.left && params.x <= params.bounds.right &&
params.y >= params.bounds.top && params.y <= params.bounds.bottom
}
// Static params
guard: { type: 'isWithinBounds', params: { x: 10, y: 20, bounds: rect } }
// Dynamic params from event (TypeScript now knows event shape)
on: {
'Mouse move': {
guard: {
type: 'isWithinBounds',
params: ({ event, context }) => ({
x: event.clientX,
y: event.clientY,
bounds: context.targetBounds
})
},
target: 'Inside'
}
}
Services and Invocations
Disposable Instances Pattern:
Create instances in invoke.input, not context. Ideal for ephemeral objects that only exist during actor lifetime.
// ❌ Anti-pattern: Storing instance in context
context: {
animationInstance: AnimationObject | null; // Hard to serialize, memory leaks
}
// ✅ Better: Store only metadata in context
context: {
duration: number;
speed: number;
target: Position;
}
// Create instance on-demand in invoke
Processing: {
entry: 'calculateMetadata', // Prepare metadata first
invoke: {
src: 'processingActor',
input: ({ context }) => {
// Create disposable instance here
const instance = new ProcessingObject({
duration: context.duration,
speed: context.speed,
target: context.target
});
return { instance, config: context };
},
onDone: { target: 'Complete' }
}
}
Benefits: Auto GC, better serialization, Stately Inspector compatible, no cleanup, prevents leaks.
Event Narrowing with assertEvent:
assertEvent in invoke input narrows event types:
invoke: {
src: 'dataActor',
input: ({ event }) => {
assertEvent(event, 'Fetch data');
// TypeScript now knows event.data exists
return { id: event.data.id };
}
}
TypeScript Integration
React Integration
Using @xstate/react
Component Architecture
State-Driven UI
Use Tags for Multiple State Checks:
Use tags instead of multiple state.matches() calls for OR logic:
// ❌ Verbose: Multiple matches
if (state.matches('loading') || state.matches('submitting') || state.matches('validating')) {
return <Spinner />;
}
// ✅ Better: Use tags
// In machine definition:
states: {
loading: { tags: 'busy' },
submitting: { tags: 'busy' },
validating: { tags: 'busy' }
}
// In component:
if (state.hasTag('busy')) {
return <Spinner />;
}
Actor Model
Passing Parent Actor Reference: Child actors receive and type parent actor ref for bidirectional communication:
// Parent machine
const parentMachine = setup({
actors: {
childActor: childMachine,
},
}).createMachine({
// ...
invoke: {
src: 'childActor',
input: ({ self }) => ({
parentRef: self, // Pass parent reference to child
}),
},
});
// Child machine
const childMachine = setup({
types: {
input: {} as {
parentRef: ActorRefFrom<typeof parentMachine>; // Type the parent ref
},
},
}).createMachine({
// Child can now send events to parent
entry: ({ input }) => {
input.parentRef.send({ type: 'Child ready' });
},
});
Testing
Visualization and Debugging
Common Patterns and Recipes
Anti-Patterns to Avoid
File Organization
Naming Conventions
State Machine Configuration:
- State names: Title Case (e.g.,
Idle,Loading Data,Processing Complete) - Event types: Sentence case (e.g.,
Submit form,Data loaded,Cancel operation) - Guard types: lowercase with spaces, natural language (e.g.,
if something meets this condition,if user is authenticated) - Action types: camelCase (e.g.,
loadData,updateContext,sendNotification) - Delay names: camelCase (e.g.,
retryDelay,debounceTimeout,pollingInterval) - Machine names: camelCase with "Machine" suffix (e.g.,
authMachine,formMachine)
Resources and References
Official Documentation:
- XState and Stately Documentation - Official docs for XState v5 and Stately tools
- XState API Reference - Complete API documentation
- Stately Studio - Visual editor for state machines
- XState GitHub - Source code and examples
Learning Resources:
- XState Catalogue - Common state machine patterns
- XState Examples - Real-world examples