Skip to content
Version: XState v4

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.