Skip to content
Version: XState v4

Guarded actions

You can use actions and guards together to run actions conditionally on transitions.

import { createMachine } from 'xstate';

const atmMachine = createMachine(
{
context: {
balance: 20,
},
initial: 'showingBalance',
states: {
showingBalance: {
on: {
WITHDRAW_TEN_DOLLARS: {
cond: 'hasAtLeastTenDollars',
actions: 'logTenDollarsComing',
},
},
},
},
},
{
guards: {
hasAtLeastTenDollars: (context) => {
return context.balance >= 10;
},
},
actions: {
logTenDollarsComing: () => {
console.log(`Here's your ten dollars!`);
},
},
}
);

In the example above, when the user withdraws ten dollars via WITHDRAW_TEN_DOLLARS, we check first if the balance in context has at least 10 dollars.

We can add an else condition to WITHDRAW_TEN_DOLLARS too:

import { createMachine } from 'xstate';

const atmMachine = createMachine(
{
context: {
balance: 20,
},
initial: 'showingBalance',
states: {
showingBalance: {
on: {
WITHDRAW_TEN_DOLLARS: [
{
cond: 'hasAtLeastTenDollars',
actions: 'logTenDollarsComing',
},
{
actions: 'sayThereIsNotEnoughInTheAccount',
},
],
},
},
},
},
{
guards: {
hasAtLeastTenDollars: (context) => {
return context.balance >= 10;
},
},
actions: {
logTenDollarsComing: () => {
console.log(`Here's your ten dollars!`);
},
sayThereIsNotEnoughInTheAccount: () => {
console.log('Not enough in the account!');
},
},
}
);

In the example above, if we have at least ten dollars in the account, we’ll log that the dollars are coming. Else, we’ll log that there’s not enough in the account.

Remember, the cond always runs before the action. XState first checks if it should run the action by running the cond, and only then runs the action.

The choose action

The choose() built-in action is an alternative API for guarded actions. choose lets you pick which actions should be executed based on some conditions inside the action itself.

The choose() approach helps you be more flexible with where you run actions. For example, you can run actions inside an entry action:

import { actions, createMachine } from 'xstate';

const logMachine = createMachine(
{
entry: actions.choose([
{
cond: 'inBrowser',
actions: 'alertUser',
},
{
actions: 'logToUser',
},
]),
},
{
guards: {
inBrowser: () => {
return typeof window === 'undefined';
},
},
actions: {
alertUser: () => {
alert('Hello, browser!');
},
logToUser: () => {
console.log('Hello, server!');
},
},
}
);

Like all built-in actions, you must apply the result of choose() directly to an actions attribute. The following example will not work:

import { actions, createMachine } from 'xstate';
const logMachine = createMachine({
entry: () => {
// 🚫 The following action just returns an object,
// it doesn’t do anything
actions.choose([
{
cond: 'inBrowser',
actions: 'alertUser',
},
{
actions: 'logToUser',
},
]);
},
});