XState Invoke Pattern
Invoke promises, callbacks, observables, and child machines as services tied to state node lifecycles
When to Use
- Fetching data when entering a state (API calls, database queries)
- Running long-lived processes tied to a state (WebSocket connections, timers)
- Delegating a sub-workflow to a child machine
- Any async operation that should start on state entry and cancel on state exit
Instructions
- Use
invokeinside a state node. The service starts when the state is entered and is automatically stopped when the state is exited. - Handle results with
onDone(success) andonError(failure) on the invoke config. - For promises, the resolved value becomes
event.datainonDone. The rejected value becomesevent.datainonError. - For callbacks, return a cleanup function — XState calls it when the state is exited.
- Define services in the machine options under
services(v4) oractors(v5) to keep machines testable and serializable. - Use
input(v5) orwithContext(v4) to pass data to invoked machines.
// data-fetch.machine.ts
import { createMachine, assign } from 'xstate';
interface FetchContext {
url: string;
data: unknown | null;
error: string | null;
}
type FetchEvent = { type: 'RETRY' } | { type: 'REFRESH' };
const fetchMachine = createMachine<FetchContext, FetchEvent>(
{
id: 'fetch',
initial: 'loading',
context: { url: '', data: null, error: null },
states: {
loading: {
invoke: {
id: 'fetchData',
src: 'fetchService',
onDone: {
target: 'success',
actions: assign({ data: (_, event) => event.data, error: null }),
},
onError: {
target: 'failure',
actions: assign({ error: (_, event) => event.data.message }),
},
},
},
success: {
on: { REFRESH: 'loading' },
},
failure: {
on: { RETRY: 'loading' },
},
},
},
{
services: {
fetchService: (ctx) =>
fetch(ctx.url).then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}),
},
}
);
// Callback service — WebSocket connection
const chatMachine = createMachine(
{
id: 'chat',
initial: 'connected',
states: {
connected: {
invoke: {
id: 'websocket',
src: 'connectWebSocket',
},
on: {
MESSAGE_RECEIVED: { actions: 'addMessage' },
DISCONNECT: 'disconnected',
},
},
disconnected: { type: 'final' },
},
},
{
services: {
connectWebSocket: (ctx) => (sendBack, onReceive) => {
const ws = new WebSocket(ctx.wsUrl);
ws.onmessage = (msg) => sendBack({ type: 'MESSAGE_RECEIVED', data: msg.data });
// onReceive listens for events sent TO this service
onReceive((event) => {
if (event.type === 'SEND_MESSAGE') ws.send(event.text);
});
// Cleanup — called when state exits
return () => ws.close();
},
},
}
);
Details
Service types in v4:
- Promise — resolves once, triggers
onDoneoronError - Callback —
(sendBack, onReceive) => cleanup— long-lived, bidirectional - Observable — emits events over time, completes triggers
onDone - Machine — child statechart,
onDonefires when child reaches final state
XState v5 equivalents: Services become actors defined with helper functions:
import { fromPromise, fromCallback } from 'xstate';
const machine = setup({
actors: {
fetchService: fromPromise(async ({ input }: { input: { url: string } }) => {
const res = await fetch(input.url);
return res.json();
}),
wsService: fromCallback(({ sendBack, input }) => {
const ws = new WebSocket(input.url);
ws.onmessage = (msg) => sendBack({ type: 'MESSAGE_RECEIVED', data: msg.data });
return () => ws.close();
}),
},
}).createMachine({
/* ... */
});
Automatic cancellation: When the state that owns the invoke exits, XState automatically stops the service. For promises, the AbortController.signal is not used automatically — you must wire it yourself if needed. For callbacks, the cleanup function runs.
Multiple invocations: A state can have multiple invoke entries (use an array). All start on entry and stop on exit.
Testing: Replace services with mocks in tests by passing { services: { fetchService: mockService } } to interpret(machine.withConfig({ services: ... })).
Source
https://stately.ai/docs/invoke
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.