XState Skill
xstate
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.
SKILL.md
Full skill instructions
XState Skill
<!-- 📏 Skill File Length Guidance: - No hard token limits - Claude Code uses progressive disclosure (loads only when needed) - Focus on clarity but favor brevity over grammatical correctness. - If this file becomes unwieldy (>500 lines or covering too many concepts), consider splitting into multiple focused skills - Current approach: One comprehensive XState skill with organized sections -->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
<!-- Add TypeScript-specific patterns --> <!-- Example: - Typing context - Typing events (discriminated unions) - Typing actions and services - Type inference helpers - Generated types from machines -->React Integration
Using @xstate/react
<!-- Add React-specific patterns --> <!-- Example: - useMachine hook patterns - useActor hook usage - useSelector for derived state - Actor providers and context -->Component Architecture
<!-- Add component organization patterns --> <!-- Example: - Where to place machine definitions - Separating machines from components - Sharing machines across components - Testing components with machines -->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
<!-- Add testing strategies --> <!-- Example: - Testing state transitions - Testing guards and actions - Mocking services - Testing React components with machines - Snapshot testing -->Visualization and Debugging
<!-- Add debugging tips --> <!-- Example: - Using @xstate/inspect - Visualizing with Stately Studio - Dev tools integration - Logging strategies -->Common Patterns and Recipes
<!-- Add your frequently-used patterns --> <!-- Example: - Form state machines - Fetch/retry patterns - Multi-step workflows - Authentication flows - Polling patterns -->Anti-Patterns to Avoid
<!-- Add things to avoid --> <!-- Example: - Overusing context instead of states - Too many nested states - Side effects in guards - Coupling machines too tightly to UI -->File Organization
<!-- Add your preferred project structure --> <!-- Example: - /machines/feature-name.machine.ts - Co-locating machines with features - Shared machines location - Type definitions location -->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
