Parent to child communication
We’ve learned that invoked actors can send events to their parent via the invoked machine’s sendParent
action and the invoked callback’s sendBack
method. Child actors can also receive events from the parent, allowing for bidirectional communication.
You must give invoked actors a unique id with invoke.id
to enable parent to child communication:
ts
import {createMachine } from 'xstate';constchildMachine =createMachine ({/* ... */});constparentMachine =createMachine ({invoke : {id : 'child',src :childMachine ,},});
ts
import {createMachine } from 'xstate';constchildMachine =createMachine ({/* ... */});constparentMachine =createMachine ({invoke : {id : 'child',src :childMachine ,},});
Once the invoked actor has an id, you can use that ID to send it events via the send
event.
In the example below, we specify that we want to send the HELLO_FROM_PARENT
event to the child
invocation after 3 seconds. The child then logs a message to the console.
ts
import {createMachine ,send } from 'xstate';constchildMachine =createMachine ({on : {HELLO_FROM_PARENT : {actions : 'logToConsole',},},},{actions : {logToConsole : () => {console .log ('Event received!');},},});constparentMachine =createMachine ({invoke : {id : 'child',src :childMachine ,},after : {3000: {actions :send ({type : 'HELLO_FROM_PARENT',},{to : 'child',}),},},});
ts
import {createMachine ,send } from 'xstate';constchildMachine =createMachine ({on : {HELLO_FROM_PARENT : {actions : 'logToConsole',},},},{actions : {logToConsole : () => {console .log ('Event received!');},},});constparentMachine =createMachine ({invoke : {id : 'child',src :childMachine ,},after : {3000: {actions :send ({type : 'HELLO_FROM_PARENT',},{to : 'child',}),},},});
Receiving events in invoked callbacks
Invoked callbacks can listen to events from the parent. To manage this, they receive an onReceive
argument.
In the example below, the parent machine sends the child ponger
actor a PING
event. The child actor can listen for that event using onReceive(listener)
and send a PONG
event back to the parent in response.
ts
import {createMachine ,send } from 'xstate';constpingPongMachine =createMachine ({initial : 'active',states : {active : {invoke : {id : 'ponger',src : 'pongActor',},entry :send ({type : 'PING' }, {to : 'ponger' }),on : {PONG : {target : 'done' },},},done : {},},},{// `actors` in v5services : {pongActor : () => (sendBack ,onReceive ) => {// Whenever parent sends 'PING',// send parent 'PONG' eventonReceive ((e ) => {if (e .type === 'PING') {sendBack ('PONG');}});},},});
ts
import {createMachine ,send } from 'xstate';constpingPongMachine =createMachine ({initial : 'active',states : {active : {invoke : {id : 'ponger',src : 'pongActor',},entry :send ({type : 'PING' }, {to : 'ponger' }),on : {PONG : {target : 'done' },},},done : {},},},{// `actors` in v5services : {pongActor : () => (sendBack ,onReceive ) => {// Whenever parent sends 'PING',// send parent 'PONG' eventonReceive ((e ) => {if (e .type === 'PING') {sendBack ('PONG');}});},},});
forwardTo
You’ll often want to use the parent machine to “forward” events to the child machine. To handle this, XState provides a built-in forwardTo
action:
ts
import {createMachine ,forwardTo } from 'xstate';constalertMachine =createMachine ({on : {ALERT : {actions : 'soundTheAlarm',},},},{actions : {soundTheAlarm : () => {alert ('Oh no!');},},});constparentMachine =createMachine ({id : 'parent',invoke : {id : 'alerter',src :alertMachine ,},on : {ALERT : {actions :forwardTo ('alerter') },},});
ts
import {createMachine ,forwardTo } from 'xstate';constalertMachine =createMachine ({on : {ALERT : {actions : 'soundTheAlarm',},},},{actions : {soundTheAlarm : () => {alert ('Oh no!');},},});constparentMachine =createMachine ({id : 'parent',invoke : {id : 'alerter',src :alertMachine ,},on : {ALERT : {actions :forwardTo ('alerter') },},});
autoForward
If you want all events sent to the parent to be forwarded to the child, you can specify autoForward: true
on an invoke
.
In the example below, any event the machine receives will be sent on to the eventHandler
:
ts
import {createMachine } from 'xstate';constmachine =createMachine ({invoke : {src : 'eventHandler',autoForward : true,},},{// `actors` in v5services : {eventHandler : () => (sendBack ,onReceive ) => {onReceive ((event ) => {// Handle the forwarded event here});},},});
ts
import {createMachine } from 'xstate';constmachine =createMachine ({invoke : {src : 'eventHandler',autoForward : true,},},{// `actors` in v5services : {eventHandler : () => (sendBack ,onReceive ) => {onReceive ((event ) => {// Handle the forwarded event here});},},});
escalate
When a parent invokes a child machine, any errors that occur in the child machine will be handled in the child. You can use the escalate
action to send that error to the parent for processing.
In the parent, you can listen for the escalate
action via the invoke.onError
transition.
In the example below, the child machine immediately escalates an error to its parent on entry
. The parent machine then processes the error in an onError
handler by logging it to the console.
ts
import {createMachine ,actions } from 'xstate';const {escalate } =actions ;constchildMachine =createMachine ({entry :escalate ({message : 'This is some error' }),});constparentMachine =createMachine ({invoke : {src :childMachine ,onError : {actions : (context ,event ) => {console .log (event .data );// data: {// message: 'This is some error'// }},},},});
ts
import {createMachine ,actions } from 'xstate';const {escalate } =actions ;constchildMachine =createMachine ({entry :escalate ({message : 'This is some error' }),});constparentMachine =createMachine ({invoke : {src :childMachine ,onError : {actions : (context ,event ) => {console .log (event .data );// data: {// message: 'This is some error'// }},},},});