XState v5 Developer Guide
SkillDev toolsComprehensive guide for XState v5 ecosystem including state machines, actors, @xstate/store, and TanStack Query integration. Use when implementing state machines, event-driven stores, client state management, or integrating XState with React and TanStack Query for data fetching orchestration.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the XState v5 Developer Guide skill
What this skill tells your AI
The instructions your AI receives, as published by pedronauck/skills in skills/curated/xstate/SKILL.md and read by ahel’s review.
This skill provides comprehensive guidelines, patterns, and best practices for working with the XState v5 ecosystem in this project, including:
- XState State Machines - Finite state machines and actors for complex application logic
- @xstate/store - Simple event-driven stores for lightweight state management
- XState + TanStack Query - Integration patterns for data fetching orchestration
Quick Reference
| Use Case | Tool | Reference |
|---|---|---|
| Complex state flows, guards, hierarchical states | XState Machines | references/machine-patterns.md |
| Simple event-driven state, atoms, undo/redo | @xstate/store | references/store-patterns.md |
| Data fetching with state orchestration | XState + TanStack Query | references/query-patterns.md |
Core Principles
XState State Machines
- Explicit States: Model application logic as explicit finite states and events
- Actor Model: Embrace the actor model for complex orchestration
- Impossible States: Use statecharts to eliminate impossible states
- Declarative Thinking: Think declaratively about state transitions
- Visualization: Use Stately Editor for visual machine creation and debugging
@xstate/store
- Event-Driven Updates: Use events for all state updates (never direct mutations)
- Keep Stores Simple: Focus on data management, not complex logic
- Optimized Subscriptions: Leverage selectors for efficient state access
- Reactive State: Use atoms for composable, reactive state
- Upgrade Path: Migrate to XState machines when complexity grows
XState + TanStack Query Integration
- XState for Orchestration: Use XState machines for workflow orchestration
- TanStack Query for Data: Use TanStack Query for data fetching and caching
- Actors as Bridges: Wrap Query operations as XState actors
- Cache Updates in Mutations: Keep query cache updates in mutation callbacks
XState State Machines
Creating a Machine with setup() API
Always use the setup() API for type-safe machines:
import { setup, createActor } from 'xstate';
export enum CounterEventType {
INCREMENT = 'increment',
DECREMENT = 'decrement',
}
const counterMachineSetup = setup({
types: {
context: {} as { count: number },
events: {} as
| { type: CounterEventType.INCREMENT }
| { type: CounterEventType.DECREMENT },
input: {} as { initialCount: number },
},
guards: {
isPositive: ({ context }) => context.count > 0,
},
});
// Define actions OUTSIDE setup using type-bound helpers
const incrementCount = counterMachineSetup.assign({
count: ({ context }) => context.count + 1,
});
export const counterMachine = counterMachineSetup.createMachine({
id: 'counter',
context: ({ input }) => ({ count: input.initialCount }),
initial: 'active',
states: {
active: {
on: {
[CounterEventType.INCREMENT]: {
actions: incrementCount,
},
},
},
},
});
React Integration
Use useMachine() for component-scoped machines and .provide() for action overrides:
import { useMachine } from '@xstate/react';
import { useMemo } from 'react';
function Counter({ onExternalAction }) {
const machine = useMemo(
() =>
counterMachine.provide({
actions: {
logEvent: () => onExternalAction(),
},
}),
[onExternalAction]
);
const [state, send] = useMachine(machine);
return (
<button onClick={() => send({ type: CounterEventType.INCREMENT })}>
Count: {state.context.count}
</button>
);
}
For detailed patterns: See references/machine-patterns.md
@xstate/store
Creating a Store
Define stores with clear initial context and event-based transitions:
import { createStore } from '@xstate/store';
const userStore = createStore({
context: {
user: null as { id: string; name: string } | null,
isLoading: false,
error: null as string | null,
},
on: {
setUser: (context, event: { user: { id: string; name: string } }) => ({
...context, // Always spread to preserve other properties
user: event.user,
error: null,
}),
setLoading: (context, event: { isLoading: boolean }) => ({
...context,
isLoading: event.isLoading,
}),
},
});
// Use the trigger API for type-safe event sending
userStore.trigger.setUser({ user: { id: '1', name: 'John' } });
React Integration
import { useSelector, useStore } from '@xstate/store/react';
function Counter() {
const store = useStore({
context: { count: 0 },
on: {
increment: (context, event: { by: number }) => ({
...context,
count: context.count + event.by,
}),
},
});
const count = useSelector(store, (state) => state.context.count);
return <button onClick={() => store.trigger.increment({ by: 1 })}>{count}</button>;
}
Undo/Redo Pattern
import { createStore } from '@xstate/store';
import { undoRedo } from '@xstate/store/undo';
const editorStore = createStore(
undoRedo({
context: { text: '' },
on: {
insertText: (context, event: { text: string }) => ({
...context,
text: context.text + event.text,
}),
},
})
);
editorStore.trigger.undo();
editorStore.trigger.redo();
For detailed patterns: See references/store-patterns.md
XState + TanStack Query Integration
Query Subscriptions with QueryObserver
Use fromCallback with QueryObserver for reactive subscriptions:
import { fromCallback } from "xstate";
import { QueryObserver, type QueryClient } from "@tanstack/react-query";
const subscribeToQuery = fromCallback(({ input, sendBack }) => {
const { queryClient, entityId } = input as {
queryClient: QueryClient;
entityId: string;
};
const observer = new QueryObserver(queryClient, {
queryKey: ["data", entityId],
queryFn: async () => { /* fetch logic */ },
});
const unsubscribe = observer.subscribe((result) => {
if (result.data) {
sendBack({ type: "REMOTE_UPDATE", data: result.data });
}
});
// Emit current result immediately
const currentResult = observer.getCurrentResult();
if (currentResult.data) {
sendBack({ type: "REMOTE_UPDATE", data: currentResult.data });
}
return () => unsubscribe();
});
Mutations with useMutation
Wrap useMutation hooks as fromPromise actors and provide via .provide():
const machine = useMemo(
() =>
createDataMachine().provide({
actors: {
saveData: fromPromise(async ({ input }) => {
return await saveMutation.mutateAsync(input.data);
}),
},
}),
[saveMutation]
);
For detailed patterns: See references/query-patterns.md
Essential Patterns (All Tools)
- Use
setup()for all machines - provides superior type inference - Use enums for event types - UPPER_SNAKE_CASE convention
- Define actions outside
setup()- use type-bound helpers likesetup().assign() - Use
enqueueActions()for sequential actions - group related actions together - Use
.provide()for React integration - override actions with external methods - Initialize context from
input- use function form for dynamic initialization - Always spread
...contextin stores - preserve other properties - Clean up subscriptions - return cleanup functions from callback actors
When to Use Each Tool
Use XState State Machines When:
- Need finite states with specific transitions
- Need guards and conditional transition logic
- Need invoked actors for async orchestration
- Need hierarchical or parallel states
- Building complex multi-step workflows
Use @xstate/store When:
- Simple global or local state management
- Event-driven state updates without complex flows
- Need undo/redo functionality
- Want a lightweight alternative with upgrade path to XState
Use XState + TanStack Query When:
- Need to orchestrate data fetching with UI state
- Building real-time features with cache synchronization
- Managing complex async workflows with server state
Common Pitfalls to Avoid
All XState Tools
- Don't use generic parameters; use
setup()instead - Don't define actions inside
setup(); define them outside using type-bound helpers - Don't use string literals for event types; use enums instead
- Don't mutate context directly; always use
assign()or return new objects - Don't make guards impure or with side effects
- Don't use deprecated v4 APIs (
interpret(),cond, etc.) - Don't forget to
.start()actors
@xstate/store Specific
- Never forget to spread
...contextin returns - Never subscribe without cleanup
- Never use stores for complex state machines (migrate to XState)
TanStack Query Integration
- Don't create QueryObserver outside callback actors
- Don't update query cache inside machine actions
- Don't pass mutation hooks directly to actors (wrap in
fromPromise) - Don't access QueryClient from closure; pass through context
Validation Checklist
Before finishing any task involving XState ecosystem:
For State Machines
- Machine uses
setup()API for type safety - Event types are defined using enums
- Actions are defined outside
setup()using type-bound helpers - Context is initialized from
inputusing function form - Guards are pure functions without side effects
- React components use
useMachine()oruseActorRef()
For @xstate/store
- Always spread
...contextin transition return values - Use
triggerAPI for type-safe event sending - Create selectors for optimized subscriptions
- Clean up subscriptions in React components
For TanStack Query Integration
- QueryObserver subscriptions are wrapped in
fromCallback - Mutations are wrapped in
fromPromiseand provided via.provide() - Query cache updates happen in mutation callbacks, not machine actions
- Cleanup functions are returned from callback actors
- QueryClient is passed through machine input/context
Always
- Run type checks (
pnpm run typecheck) - Run tests (
pnpm run test)
Detailed Reference Documentation
For comprehensive patterns, examples, and in-depth guidance, consult these reference files:
references/machine-patterns.md- Complete XState machine patterns including hierarchical states, parallel states, actors, testing, and migration from v4references/store-patterns.md- Full @xstate/store patterns including selectors, atoms, effects, undo/redo, and when to migrate to machinesreferences/query-patterns.md- Complete TanStack Query integration patterns with QueryObserver, mutations, and full machine examples
Signals
- GitHub stars
- 611
- Forks
- 88
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
xstate- Source
- github.com/pedronauck/skills