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);
},
},
}
);