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
Define an actor that wraps your async function:
actors: {
fetchUser: fromPromise(({ input }: { input: { name: string } }) =>
getGreeting(input.name)
)
}
Specify the shape of your context data:
types: {
context: {} as {
name: string;
data: { greeting: string } | null;
}
}
Use invoke in the loading state:
loading: {
invoke: {
src: 'fetchUser',
input: ({ context }) => ({ name: context.name }),
onDone: {
target: 'success',
actions: assign({
data: ({ event }) => event.output
})
},
onError: 'failure'
}
}
Handle success and failure
Transition to appropriate states:
onDone: Assign the result to context and go to success
onError: Go to failure state
Add automatic or manual retry:
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
- Type your context: Use TypeScript for better autocomplete and safety
- Handle errors: Always provide an
onError transition
- Provide retry: Let users recover from failures
- Show loading states: Keep users informed during async operations
- Cancel on exit: Invoked actors are automatically canceled when leaving the state