Skip to content
Version: XState v4

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:

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

const parentMachine = 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.

import { createMachine, send } from 'xstate';

const childMachine = createMachine(
{
on: {
HELLO_FROM_PARENT: {
actions: 'logToConsole',
},
},
},
{
actions: {
logToConsole: () => {
console.log('Event received!');
},
},
},
);

const parentMachine = 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.

import { createMachine, send } from 'xstate';

const pingPongMachine = createMachine(
{
initial: 'active',
states: {
active: {
invoke: {
id: 'ponger',
src: 'pongActor',
},
entry: send({ type: 'PING' }, { to: 'ponger' }),
on: {
PONG: { target: 'done' },
},
},
done: {},
},
},
{
// `actors` in v5
services: {
pongActor: () => (sendBack, onReceive) => {
// Whenever parent sends 'PING',
// send parent 'PONG' event
onReceive((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:

import { createMachine, forwardTo } from 'xstate';

const alertMachine = createMachine(
{
on: {
ALERT: {
actions: 'soundTheAlarm',
},
},
},
{
actions: {
soundTheAlarm: () => {
alert('Oh no!');
},
},
},
);

const parentMachine = 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:

import { createMachine } from 'xstate';

const machine = createMachine(
{
invoke: {
src: 'eventHandler',
autoForward: true,
},
},
{
// `actors` in v5
services: {
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.

import { createMachine, actions } from 'xstate';

const { escalate } = actions;

const childMachine = createMachine({
entry: escalate({ message: 'This is some error' }),
});

const parentMachine = createMachine({
invoke: {
src: childMachine,
onError: {
actions: (context, event) => {
console.log(event.data);
// data: {
// message: 'This is some error'
// }
},
},
},
});