Built-in actions
Along with the assign
action, XState has several other built-in actions which can do different things in a state machine. We’ll introduce a couple of built-in actions for now and learn about the others later.
Send action
XState’s built-in send
action is useful for when statecharts need to send events back to themselves.
When the send action is executed, it sends an event back to the machine as if it were from an external source.
This pattern can help compose different flows together. In the example below, the user can either press the copy
button or press ctrl + c
to fire a COPY
event to the machine. Using the send
action to fire the same event from both actions reduces duplication.
import { createMachine, send } from 'xstate';
const keyboardShortcutMachine = createMachine({
on: {
PRESS_COPY_BUTTON: {
actions: send({ type: 'COPY' }),
},
PRESS_CTRL_C: {
actions: send({ type: 'COPY' }),
},
COPY: {
actions: 'copyToClipboard',
},
},
});
You can also dynamically specify the event to send by passing a function to send
:
send((context, event) => {
return {
type: 'SOME_EVENT',
};
});
Sending events to actors
With the sendTo
action, events can be sent to actors:
import { actions, AnyActorRef, assign, createMachine, spawn } from 'xstate';
const { sendTo } = actions;
const machine = createMachine({
schema: { context: {} as { someRef: AnyActorRef } },
states: {
active: {
entry: assign({
someRef: () => spawn(someMachine),
}),
on: {
SOME_EVENT: {
actions: (context) =>
sendTo(context.someRef, { type: 'PING' }),
},
},
},
},
});
Raise action
The raise
action creator queues an event to the statechart, in the internal event queue.
This means the event is sent immediately on the current “step” of the interpreter.
import { createMachine, actions } from "xstate";
const { raise } = actions;
// Demonstrate `raise` action
const raiseActionDemo = createMachine({
id: 'Raise action demo',
initial: 'entry',
states: {
entry: {
on: {
STEP: {
target: 'middle',
},
RAISE: {
target: 'middle',
// immediately invoke the NEXT event in 'middle'
actions: raise('NEXT')
},
},
},
middle: {
on: {
NEXT: 'last'
},
},
last: {
on: {
RESET: 'entry'
},
},
},
})
Click on both STEP
and RAISE
events in the visualizer to see the difference.
Pure action
The pure
action is useful when you need to run a dynamic number of actions depending on the current machine’s state.
pure
lets you pass a function to the machine, which calculates the type and number of actions to be executed.
In the example below, we check context
to find which actions the machine should run.
import { actions, createMachine } from 'xstate';
const { pure } = actions;
createMachine({
context: {
runBothActions: false,
},
entry: pure((context) => {
if (context.runBothActions) {
// You can return an array of actions
return ['action1', 'action2'];
}
// Or a single action
return 'action1';
}),
});
Log action
The log
action creator is a declarative way of logging anything related to the current state context
and/or event
. |
import { createMachine, actions } from 'xstate';
const { log } = actions;
const loggingMachine = createMachine({
id: 'logging',
context: { count: 42 },
initial: 'start',
states: {
start: {
entry: log('started!'),
on: {
FINISH: {
target: 'end',
actions: log(
(context, event) =>
`count: ${context.count}, event: ${event.type}`,
'Finish label'
)
}
}
},
end: {}
}
});
const endState = loggingMachine.transition('start', 'FINISH');
endState.actions;
// the endState.actions array will now contain our log action:
// [
// {
// type: 'xstate.log',
// label: 'Finish label',
// expr: (context, event) => ...
// }
// ]
// The interpreter would log the action's evaluated expression
// based on the current state context and event.
Without any arguments, log
is an action that logs an object with context
and event
properties, containing the current context and triggering event, respectively.
Choose action
The choose
action creator creates an action that specifies which actions should be executed based on some conditions.
import { actions } from "xstate";
const { choose, log } = actions;
const maybeDoThese = choose([
{
cond: "cond1",
actions: [
// selected when "cond1" is true
log("cond1 chosen!")
]
},
{
cond: "cond2",
actions: [
// selected when "cond1" is false and "cond2" is true
log((context, event) => {
/* ... */
}),
log("another action")
]
},
{
cond: (context, event) => {
// some condition
return false;
},
actions: [
// selected when "cond1" and "cond2" are false and the inline `cond` is true
(context, event) => {
// some other action
}
]
},
{
actions: [
log("fall-through action")
// selected when "cond1", "cond2", and "cond3" are false
]
}
]);
This is analogous to the SCXML <if>
, <elseif>
, and <else>
elements: www.w3.org/TR/scxml/#if
Rules of built-in actions
Built-in actions are pure functions. Pure functions don’t execute anything themselves but instead return instructions that tells XState what to do.
For example, the assign function returns an object containing type: 'xstate.assign'
and an assigner
function.
import { assign } from 'xstate';
const assignResult = assign((context, event) => ({
newValue: true,
}));
assignResult.type; // 'xstate.assign'
assignResult.assigner; // (context, event) => ({ newValue: true })
The instruction set above is interpreted by XState, which executes the code. The result of assign
must be passed directly to actions
, entry
or exit
.
For example, the following code won’t work correctly because the result of the assign
isn’t being passed into assignToContext
.
import { createMachine, assign } from 'xstate';
const machine = createMachine(
{
// ...config
},
{
actions: {
assignToContext: (context, event) => {
// 🚫 This won’t work!
// The result of the assign isn’t being passed
// into assignToContext
assign({
message: 'Hello!',
});
},
},
}
);
The following example works correctly because the result of the assign
is passed into assignToContext
:
import { createMachine, assign } from 'xstate';
const machine = createMachine(
{
// ...config
},
{
actions: {
assignToContext: assign((context) => ({
message: 'Hello!',
})),
},
}
);
Summary
send
can be used to send events back to your machine. pure
can be used to dynamically return different actions. Built-in actions must be passed directly to the machine or returned from pure
.