Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/statelyai/xstate/llms.txt

Use this file to discover all available pages before exploring further.

This example demonstrates how to fetch data asynchronously using promise actors, with proper loading, success, and error states.

Overview

The fetch machine models a complete async data flow:
  • idle - Waiting to fetch
  • loading - Fetching data
  • success - Data loaded successfully
  • failure - Error occurred (with auto-retry)

Machine Definition

import { assign, fromPromise, setup } from 'xstate';
import { getGreeting } from '.';

export const fetchMachine = setup({
  types: {
    context: {} as {
      name: string;
      data: {
        greeting: string;
      } | null;
    }
  },
  actors: {
    fetchUser: fromPromise(({ input }: { input: { name: string } }) =>
      getGreeting(input.name)
    )
  }
}).createMachine({
  initial: 'idle',
  context: {
    name: 'World',
    data: null
  },
  states: {
    idle: {
      on: {
        FETCH: 'loading'
      }
    },
    loading: {
      invoke: {
        src: 'fetchUser',
        input: ({ context }) => ({ name: context.name }),
        onDone: {
          target: 'success',
          actions: assign({
            data: ({ event }) => event.output
          })
        },
        onError: 'failure'
      }
    },
    success: {},
    failure: {
      after: {
        1000: 'loading'
      },
      on: {
        RETRY: 'loading'
      }
    }
  }
});

Implementation

1
Create a promise actor
2
Define an actor that wraps your async function:
3
actors: {
  fetchUser: fromPromise(({ input }: { input: { name: string } }) =>
    getGreeting(input.name)
  )
}
4
Define context types
5
Specify the shape of your context data:
6
types: {
  context: {} as {
    name: string;
    data: { greeting: string } | null;
  }
}
7
Invoke the actor
8
Use invoke in the loading state:
9
loading: {
  invoke: {
    src: 'fetchUser',
    input: ({ context }) => ({ name: context.name }),
    onDone: {
      target: 'success',
      actions: assign({
        data: ({ event }) => event.output
      })
    },
    onError: 'failure'
  }
}
10
Handle success and failure
11
Transition to appropriate states:
12
  • onDone: Assign the result to context and go to success
  • onError: Go to failure state
  • 13
    Implement retry logic
    14
    Add automatic or manual retry:
    15
    failure: {
      after: {
        1000: 'loading' // Auto-retry after 1 second
      },
      on: {
        RETRY: 'loading' // Manual retry
      }
    }
    

    Complete Example

    import { createActor } from 'xstate';
    import { fetchMachine } from './fetchMachine';
    
    // Async function that may fail
    export async function getGreeting(name: string): Promise<{ greeting: string }> {
      return new Promise((res, rej) => {
        setTimeout(() => {
          if (Math.random() < 0.5) {
            rej();
            return;
          }
          res({
            greeting: `Hello, ${name}!`
          });
        }, 1000);
      });
    }
    
    const fetchActor = createActor(fetchMachine);
    fetchActor.subscribe((state) => {
      console.log('Value:', state.value);
      console.log('Context:', state.context);
    });
    fetchActor.start();
    
    fetchActor.send({ type: 'FETCH' });
    

    Usage with React

    import { useMachine } from '@xstate/react';
    import { fetchMachine } from './fetchMachine';
    
    function UserGreeting() {
      const [state, send] = useMachine(fetchMachine);
    
      return (
        <div>
          {state.matches('idle') && (
            <button onClick={() => send({ type: 'FETCH' })}>
              Fetch Greeting
            </button>
          )}
          
          {state.matches('loading') && <p>Loading...</p>}
          
          {state.matches('success') && (
            <p>{state.context.data?.greeting}</p>
          )}
          
          {state.matches('failure') && (
            <div>
              <p>Error loading data</p>
              <button onClick={() => send({ type: 'RETRY' })}>
                Retry
              </button>
            </div>
          )}
        </div>
      );
    }
    

    Key Concepts

    • fromPromise(): Converts promises into actors
    • invoke: Runs actors when entering a state
    • onDone/onError: Handle actor completion
    • input: Pass data to invoked actors
    • event.output: Access the promise result
    • after: Schedule delayed transitions

    Best Practices

    1. Type your context: Use TypeScript for better autocomplete and safety
    2. Handle errors: Always provide an onError transition
    3. Provide retry: Let users recover from failures
    4. Show loading states: Keep users informed during async operations
    5. Cancel on exit: Invoked actors are automatically canceled when leaving the state

    Build docs developers (and LLMs) love