Machines
Callback actors are one way to start invoking long-lived actors and are useful for simple to intermediate cases. But sometimes, you’ll want to use all the power of a statechart.
Machine actors are valuable for such use cases. You can pass a machine directly to the invoke
src
property to invoke the machine in that state.
import { createMachine } from 'xstate';
const childMachine = createMachine({
/* ... */
});
const parentMachine = createMachine({
invoke: {
src: childMachine,
},
});
Just like with callback actors, the child machine is stopped if the state where it’s invoked is exited.
Machine actors enable you to create machines that can act as reusable modules across your application.
sendParent
Child machines can communicate to the parent that invoked them via the sendParent
action.
import { createMachine, sendParent } from 'xstate';
const childMachine = createMachine({
after: {
3000: {
actions: sendParent({
type: 'TIMER_DONE',
}),
},
},
});
const parentMachine = createMachine({
initial: 'waiting',
states: {
waiting: {
on: {
TIMER_DONE: {
target: 'complete',
},
},
invoke: {
src: childMachine,
},
},
complete: {
type: 'final',
},
},
});
In the example above, the parentMachine
invokes the childMachine
in the waiting
state. Three seconds after being started, the childMachine
sends the TIMER_DONE
event to its parent. The TIMER_DONE
transition in the parentMachine
puts the machine into the complete
state.
Only use sendParent on child machines
If the child machine doesn’t have a parent, for instance, if the machine is run with interpret
, then sendParent
will throw an error.
onDone in child machines
When a child machine reaches a final state, it reports that it’s done to its parent. The parent can listen for this event with the invoke.onDone
property:
const childMachine = createMachine({
initial: 'waiting',
states: {
waiting: {
after: {
3000: {
target: 'complete',
},
},
},
complete: {
type: 'final',
},
},
});
const parentMachine = createMachine(
{
invoke: {
src: childMachine,
onDone: {
actions: 'logComplete',
},
},
},
{
actions: {
logComplete: () => {
console.log('Child machine complete!');
},
},
}
);
You can also send data along with this onDone
event. This “done data” is specified on the final state’s data
property:
const secretMachine = createMachine({
initial: 'wait',
context: {
secret: '42',
},
states: {
wait: {
after: {
1000: { target: 'reveal' },
},
},
reveal: {
type: 'final',
data: {
secret: (context, event) => context.secret,
},
},
},
});
const parentMachine = createMachine({
context: {
revealedSecret: undefined,
},
invoke: {
src: secretMachine,
onDone: {
actions: assign({
revealedSecret: (context, event) => {
// { type: 'done.invoke.<id>', data: { secret: '42' } }
return event.data.secret;
},
}),
},
},
});