XState Invoke Pattern

VerifiedSafe

Learn to use XState's invoke pattern to handle promises, callbacks, observables, and child machines tied to state lifecycle.

Sby Skills Guide Bot
DevelopmentIntermediate
7/23/2026
Claude CodeCursorWindsurfCopilotCodex
#xstate#state-machines#async#actors#typescript

Recommended for

Automated summary

Explains how to use XState's invoke to run promises, callbacks, observables, and child machines bound to a state node's lifecycle.

Strengths

  • Covers both promise and callback services with concrete, runnable examples (HTTP fetch, WebSocket)
  • Maps v4 services to v5 actors using fromPromise and fromCallback helpers
  • Shows onDone/onError handling combined with assign to update context
  • Clarifies the lifecycle: starts on state entry, cancels or cleans up on exit

Limitations

  • The v5 example is cut off, and observable and child-machine cases lack full code
  • Assumes prior familiarity with TypeScript and core XState concepts
  • Does not cover advanced error recovery or retry strategies beyond a simple RETRY event
When to use it

Reach for it when wiring async work (API calls, sockets, timers, sub-workflows) into a statechart that needs clean entry/exit handling.

When not to use it

Skip it if you're not using XState, or if you need a general introduction to state machines rather than the invoke pattern.

Security analysis

Safe
Quality score88/100

This is a reference document about XState patterns with no declared tools or executable instructions. It contains only TypeScript examples and documentation, so it poses no meaningful execution or exfiltration risk.

No concerns found

How this analysis is made

Examples

Fetch on entry with invoke
Refactor my loading state so the API call uses XState invoke with onDone and onError, storing the result and error in context via assign.
WebSocket callback service
Write an XState callback service used with invoke that opens a WebSocket, sends incoming messages back to the machine, and closes the socket when the state exits.
Migrate services to v5 actors
Convert my machine's services option into XState v5 actors using setup() with fromPromise and fromCallback.

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

  1. Use invoke inside a state node. The service starts when the state is entered and is automatically stopped when the state is exited.
  2. Handle results with onDone (success) and onError (failure) on the invoke config.
  3. For promises, the resolved value becomes event.data in onDone. The rejected value becomes event.data in onError.
  4. For callbacks, return a cleanup function — XState calls it when the state is exited.
  5. Define services in the machine options under services (v4) or actors (v5) to keep machines testable and serializable.
  6. Use input (v5) or withContext (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 onDone or onError
  • Callback — (sendBack, onReceive) => cleanup — long-lived, bidirectional
  • Observable — emits events over time, completes triggers onDone
  • Machine — child statechart, onDone fires 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

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. 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.

From the file to a first result

The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.

View the source and supporting files

This download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.

1. Download the file

Download SKILL.md

From the root of a test project, save the file as .skill-downloads/xstate-invoke-pattern-mrx3xz77/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.

Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.

mkdir -p '.skill-downloads/xstate-invoke-pattern-mrx3xz77' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/xstate-invoke-pattern-mrx3xz77/download' --output '.skill-downloads/xstate-invoke-pattern-mrx3xz77/SKILL.md.part' && mv '.skill-downloads/xstate-invoke-pattern-mrx3xz77/SKILL.md.part' '.skill-downloads/xstate-invoke-pattern-mrx3xz77/SKILL.md'

2. Check prerequisites

Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.

No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.

The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.

3. Load the skill

This file does not provide a usable name/description header for this native procedure. It remains available to download and inspect; use the manual method below.

Manual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.

4. Try a first task

Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:

Read .skill-downloads/xstate-invoke-pattern-mrx3xz77/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.

Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.

Related skills