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
:
ts
import {createMachine ,spawn ,assign } from 'xstate';ÂconstchildMachine =createMachine ({/* ... */});ÂconstparentMachine =createMachine ({entry : [assign ({childMachineRef : () =>spawn (childMachine ),}),],});
ts
import {createMachine ,spawn ,assign } from 'xstate';ÂconstchildMachine =createMachine ({/* ... */});ÂconstparentMachine =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:
ts
constparentMachine =createMachine ({entry : [assign ({childMachineRefs : () => [spawn (childMachine ),spawn (childMachine ),spawn (childMachine ),],}),],});
ts
constparentMachine =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
:
ts
send({ type: 'INC' }, { to: (context) => context.counterRef });forwardTo(context => context.counterRef);
ts
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
:
ts
constmachine =createMachine ({entry :assign ((context ) => ({counterRef :spawn (childMachine , {autoForward : true,}),})),});
ts
constmachine =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.
ts
import {createMachine ,spawn ,assign ,actions ,ActorRefFrom } from 'xstate';Âconst {stop } =actions ;ÂconstparentMachine =createMachine ({/*** In TypeScript, you can use the ActorRefFrom helper* to type the machine*/schema : {context : {} as {childMachineRef :ActorRefFrom <typeofchildMachine >;},},entry : [assign ({childMachineRef : () =>spawn (childMachine ),}),],on : {STOP : {actions : 'stopMachine',},},},{actions : {stopMachine :stop ((context ) =>context .childMachineRef ),},});
ts
import {createMachine ,spawn ,assign ,actions ,ActorRefFrom } from 'xstate';Âconst {stop } =actions ;ÂconstparentMachine =createMachine ({/*** In TypeScript, you can use the ActorRefFrom helper* to type the machine*/schema : {context : {} as {childMachineRef :ActorRefFrom <typeofchildMachine >;},},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.
ts
import {createMachine ,assign ,spawn } from 'xstate';Âconstmachine =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};}),}),});
ts
import {createMachine ,assign ,spawn } from 'xstate';Âconstmachine =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:
ts
import {createMachine ,assign ,spawn } from 'xstate';import {interval } from 'rxjs';import {map } from 'rxjs/operators';ÂconstcreateCounterObservable = (ms : number) =>interval (ms ).pipe (map ((count ) => ({type : 'COUNT.UPDATE',count })));Âconstmachine =createMachine ({context : {ms : 1000 },entry :assign ({counterRef : ({ms }) =>spawn (createCounterObservable (ms )),}),on : {'COUNT.UPDATE': {actions : 'logCount',},},},{actions : {logCount : (context ,event ) => {console .log (event .count );},},});
ts
import {createMachine ,assign ,spawn } from 'xstate';import {interval } from 'rxjs';import {map } from 'rxjs/operators';ÂconstcreateCounterObservable = (ms : number) =>interval (ms ).pipe (map ((count ) => ({type : 'COUNT.UPDATE',count })));Âconstmachine =createMachine ({context : {ms : 1000 },entry :assign ({counterRef : ({ms }) =>spawn (createCounterObservable (ms )),}),on : {'COUNT.UPDATE': {actions : 'logCount',},},},{actions : {logCount : (context ,event ) => {console .log (event .count );},},});