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.
Actors are independent entities that can receive and send messages, maintain internal state, and spawn other actors. XState provides several actor logic creators for different use cases.
What are Actors?
In XState, actors are self-contained units of logic that:
- Receive events
- Maintain their own state (context)
- Send events to other actors
- Can be started, stopped, and supervised
- Communicate exclusively through message passing
import { createActor, fromPromise } from 'xstate';
const fetchLogic = fromPromise(async () => {
const response = await fetch('https://api.example.com/data');
return response.json();
});
const actor = createActor(fetchLogic);
actor.subscribe((snapshot) => {
console.log(snapshot);
});
actor.start();
Actor Types
XState provides four primary actor logic creators:
Promise Actors
For asynchronous operations that resolve once with a value.
import { fromPromise } from 'xstate';
const promiseLogic = fromPromise(async ({ input }) => {
const result = await fetch(input.url);
return result.json();
});
Learn more about fromPromise
Callback Actors
For subscription-based or event-driven logic.
import { fromCallback } from 'xstate';
const callbackLogic = fromCallback(({ sendBack, receive }) => {
const handler = (event) => sendBack(event);
document.addEventListener('click', handler);
return () => {
document.removeEventListener('click', handler);
};
});
Learn more about fromCallback
Observable Actors
For reactive streams and observables (RxJS compatible).
import { fromObservable } from 'xstate';
import { interval } from 'rxjs';
const observableLogic = fromObservable(() => interval(1000));
Learn more about fromObservable
Transition Actors
For reducer-style state management.
import { fromTransition } from 'xstate';
const transitionLogic = fromTransition(
(state, event) => {
if (event.type === 'increment') {
return { count: state.count + 1 };
}
return state;
},
{ count: 0 }
);
Learn more about fromTransition
Creating and Using Actors
Basic Actor Lifecycle
import { createActor, fromPromise } from 'xstate';
const logic = fromPromise(async () => 'Hello!');
// 1. Create actor
const actor = createActor(logic);
// 2. Subscribe to snapshots
actor.subscribe((snapshot) => {
console.log('Status:', snapshot.status);
console.log('Output:', snapshot.output);
});
// 3. Start actor
actor.start();
// 4. Eventually stop (if needed)
// actor.stop();
All actor types can receive input:
import { createActor, fromPromise } from 'xstate';
const logic = fromPromise<string, { name: string }>(async ({ input }) => {
return `Hello, ${input.name}!`;
});
const actor = createActor(logic, {
input: { name: 'World' }
});
actor.start();
Invoking Actors in Machines
Actors are commonly invoked from state machines:
import { setup, fromPromise } from 'xstate';
const fetchUser = fromPromise(async ({ input }: { input: { id: string } }) => {
const response = await fetch(`/api/users/${input.id}`);
return response.json();
});
const machine = setup({
actors: {
fetchUser
}
}).createMachine({
initial: 'loading',
states: {
loading: {
invoke: {
src: 'fetchUser',
input: { id: '123' },
onDone: {
target: 'success',
actions: ({ event }) => {
console.log('User:', event.output);
}
},
onError: 'failure'
}
},
success: {},
failure: {}
}
});
Actor Communication
Sending Events to Parent
import { fromCallback } from 'xstate';
const logic = fromCallback(({ sendBack }) => {
setTimeout(() => {
sendBack({ type: 'TIMEOUT' });
}, 1000);
});
Receiving Events
import { fromCallback } from 'xstate';
const logic = fromCallback(({ receive }) => {
receive((event) => {
if (event.type === 'PING') {
console.log('Received ping!');
}
});
});
Sending Events to Actors
import { createActor, fromCallback } from 'xstate';
const logic = fromCallback(({ receive }) => {
receive((event) => {
console.log('Received:', event.type);
});
});
const actor = createActor(logic);
actor.start();
actor.send({ type: 'CUSTOM_EVENT' });
Actor Snapshots
Each actor type produces snapshots with different properties:
Promise Snapshot
interface PromiseSnapshot<TOutput, TInput> {
status: 'active' | 'done' | 'error' | 'stopped';
output: TOutput | undefined;
error: unknown;
input: TInput | undefined;
}
Callback Snapshot
interface CallbackSnapshot<TInput> {
status: 'active' | 'stopped';
output: undefined;
error: undefined;
input: TInput;
}
Observable Snapshot
interface ObservableSnapshot<TContext, TInput> {
status: 'active' | 'done' | 'error' | 'stopped';
context: TContext | undefined;
output: undefined;
error: unknown;
input: TInput | undefined;
}
Transition Snapshot
interface TransitionSnapshot<TContext> {
status: 'active';
context: TContext;
output: undefined;
error: undefined;
}
Actor System
Actors exist within an actor system that manages communication:
import { createActor, fromCallback } from 'xstate';
const logic = fromCallback(({ system }) => {
// Access the actor system
console.log('System:', system);
});
const actor = createActor(logic);
actor.start();
Emitting Custom Events
Actors can emit custom events to subscribers:
import { createActor, fromPromise } from 'xstate';
const logic = fromPromise(async ({ emit }) => {
emit({ type: 'progress', value: 0.5 });
// ... async work
emit({ type: 'progress', value: 1.0 });
return 'done';
});
const actor = createActor(logic);
actor.subscribe({
next: (snapshot) => console.log('Snapshot:', snapshot),
error: (err) => console.error('Error:', err),
complete: () => console.log('Complete')
});
actor.on('progress', (event) => {
console.log('Progress:', event.value);
});
actor.start();
Best Practices
-
Choose the right actor type:
- Use
fromPromise for one-time async operations
- Use
fromCallback for subscriptions and event listeners
- Use
fromObservable for reactive streams
- Use
fromTransition for reducer-style state management
-
Clean up resources: Return cleanup functions from callback actors
-
Type your actors: Provide type parameters for type safety
-
Handle errors: Subscribe to error events or use onError handlers
-
Use input for configuration: Pass initial data via input rather than closures
See Also