Skip to content
Version: XState v4

Spawning actors

Sometimes invoking actors may not be flexible enough for your needs. In such cases, you might want to:

  • Invoke child machines that last across several states when machines invoked with invoke are tied to that state
  • Invoke a dynamic number of actors

You can use a powerful tool called spawn to run these actors in these cases. Actors created with spawn are spawning actors, and actors created with invoke are invoking actors.

Spawning actors puts a reference to the machine in context, which means that you must always assign a spawned actor to context via assign:

import { createMachine, spawn, assign } from 'xstate';

const childMachine = createMachine({
/* ... */
});

const parentMachine = createMachine({
entry: [
assign({
childMachineRef: () => spawn(childMachine),
}),
],
});

In the example above, the spawned actor can now be referenced on the context of the machine. You can spawn as many actors as you need:

import { createMachine, spawn, assign } from 'xstate';

const childMachine = createMachine({
/* ... */
});

const parentMachine = createMachine({
entry: [
assign({
childMachineRefs: () => [
spawn(childMachine),
spawn(childMachine),
spawn(childMachine),
],
}),
],
});

Sending events to spawned machines​

Events can be sent to spawned actors by passing a function to send or forwardTo:

send({ type: 'INC' }, { to: (context) => context.counterRef });

forwardTo(context => context.counterRef);

You can also forward all events to the child by passing autoForward as an option to spawn:

import { spawn, createMachine, assign } from 'xstate';

const childMachine = createMachine({});

const machine = createMachine({
entry: assign((context) => ({
counterRef: spawn(childMachine, {
autoForward: true,
}),
})),
});

Passing autoForward will ensure that every event sent to the machine also gets forwarded to childMachine.

Stopping spawned actors​

When you want to stop a spawned actor, you can either stop the parent machine, which will stop all child actors automatically, or stop the actor via the stop action.

const childMachine = createMachine({
/* ... */
});

import { createMachine, spawn, assign, actions, ActorRefFrom } from 'xstate';
const { stop } = actions;

const parentMachine = createMachine(
{
/**
* In TypeScript, you can use the ActorRefFrom helper
* to type the machine
*/
schema: {
context: {} as {
childMachineRef: ActorRefFrom<typeof childMachine>;
},
},
entry: [
assign({
childMachineRef: () => spawn(childMachine),
}),
],
on: {
STOP: {
actions: 'stopMachine',
},
},
},
{
actions: {
stopMachine: stop((context) => context.childMachineRef),
},
}
);

Spawning callbacks​

Just like invoking callbacks, callbacks can be spawned as actors.

import { createMachine, assign, spawn } from 'xstate';

const machine = createMachine({
entry: assign({
counterRef: (context, event) =>
spawn((sendBack, receive) => {
// Run any code you want inside here

return () => {
// Any code inside here will be called when
// you leave this state, or the machine is stopped
};
}),
}),
});

Spawned callbacks behave exactly the same as invoked callbacks but with all the flexibility of spawn.

Spawning observables​

Just like invoking observables, observables can be spawned as actors:

import { createMachine, assign, spawn } from 'xstate';
import { interval } from 'rxjs';
import { map } from 'rxjs/operators';

const createCounterObservable = (ms: number) =>
interval(ms).pipe(map((count) => ({ type: 'COUNT.UPDATE', count })));

const machine = createMachine(
{
context: { ms: 1000 },
entry: assign({
counterRef: ({ ms }) => spawn(createCounterObservable(ms)),
}),
on: {
'COUNT.UPDATE': {
actions: 'logCount',
},
},
},
{
actions: {
logCount: (context, event) => {
console.log(event.count);
},
},
}
);