Skip to content

Actions

Statecharts give you a great deal of control over running side effects in your app. The first method is through actions.

Side effects

You may have come across a type of function called a pure function. A function is ‘pure’ if it takes in an input, returns a predictable output, and does nothing else. Pure functions do not involve:

  • Waiting a set amount of time
  • Making an API call
  • Logging things to the console

We can think of the processes above as side effects of our program running. The name gives them a negative medical connotation, but they’re incredibly important. Processes that don’t have side effects don’t talk to anything external, don’t worry about time, and don’t react to unexpected errors.

Actions

Actions are side effects which are:

  • Fire-and-forget: Actions don’t talk back to the statechart.
  • Unlikely to fail. Actions are unlikely to impact the system if they fail.
  • Usually synchronous. For example, you don’t await actions.

Some examples of actions are:

  • Logging something to the console
  • Assigning a value to a variable
  • Changing the attribute of a DOM node

You can fire an action upon entering or exiting a state by using the entry and exit attributes on that state.

ts
import { createMachine } from 'xstate';
 
const machine = createMachine(
{
initial: 'visiting',
states: {
visiting: {
entry: 'sayHello',
exit: 'sayGoodbye',
on: {
LEAVE: 'notVisiting',
},
},
notVisiting: {},
},
},
{
actions: {
sayHello: () => {
console.log('Hello');
},
sayGoodbye: () => {
console.log('Goodbye');
},
},
}
);
ts
import { createMachine } from 'xstate';
 
const machine = createMachine(
{
initial: 'visiting',
states: {
visiting: {
entry: 'sayHello',
exit: 'sayGoodbye',
on: {
LEAVE: 'notVisiting',
},
},
notVisiting: {},
},
},
{
actions: {
sayHello: () => {
console.log('Hello');
},
sayGoodbye: () => {
console.log('Goodbye');
},
},
}
);

You can also fire an action on a transition:

ts
import { createMachine } from 'xstate';
 
const machine = createMachine(
{
initial: 'toggledOn',
states: {
toggledOn: {
on: {
TOGGLE: {
target: 'toggledOff',
actions: 'sayToggled',
},
},
},
toggledOff: {
on: {
TOGGLE: {
target: 'toggledOn',
actions: 'sayToggled',
},
},
},
},
},
{
actions: {
sayToggled: () => {
console.log('Toggled!');
},
},
}
);
ts
import { createMachine } from 'xstate';
 
const machine = createMachine(
{
initial: 'toggledOn',
states: {
toggledOn: {
on: {
TOGGLE: {
target: 'toggledOff',
actions: 'sayToggled',
},
},
},
toggledOff: {
on: {
TOGGLE: {
target: 'toggledOn',
actions: 'sayToggled',
},
},
},
},
},
{
actions: {
sayToggled: () => {
console.log('Toggled!');
},
},
}
);

Anywhere you can use an action, you can also declare it as an array to express multiple actions.

ts
import { createMachine } from 'xstate';
 
const machine = createMachine(
{
entry: ['youSayGoodbye', 'iSayHello'],
},
{
actions: {
iSayHello: () => {
console.log('Me: Hello');
},
youSayGoodbye: () => {
console.log('You: Goodbye');
},
},
}
);
ts
import { createMachine } from 'xstate';
 
const machine = createMachine(
{
entry: ['youSayGoodbye', 'iSayHello'],
},
{
actions: {
iSayHello: () => {
console.log('Me: Hello');
},
youSayGoodbye: () => {
console.log('You: Goodbye');
},
},
}
);

In the example above, Me: Hello will be logged to the console, followed by You: Goodbye.

Actions on self-transitions

A self-transition is when an event happens, but the transition returns to the same state. The transition arrow exits and re-enters the same state.

A helpful way to describe a self-transition is “doing something, not going somewhere” in the process.

In a dog begging process, there would be a begging state with a gets treat event. And for the dogs who love their food, no matter how many times you go through the gets treat event, the dog returns to its begging state.

Dog begging machine with one begging state and a ‘gets treat’ transition which leaves and returns to the same state.Dog begging machine with one begging state and a ‘gets treat’ transition which leaves and returns to the same state.

Self-transitions are helpful when you want to fire an action, but not leave your current state. You can use an action on the transition to fire it whenever that event is received.

ts
import { createMachine } from 'xstate';
 
createMachine(
{
initial: 'begging',
states: {
begging: {
on: {
'gets treat': {
actions: 'makeHappySnufflingSound',
},
},
},
},
},
{
actions: {
makeHappySnufflingSound: () => {
console.log('Snuffle snuffle snuffle');
},
},
}
);
ts
import { createMachine } from 'xstate';
 
createMachine(
{
initial: 'begging',
states: {
begging: {
on: {
'gets treat': {
actions: 'makeHappySnufflingSound',
},
},
},
},
},
{
actions: {
makeHappySnufflingSound: () => {
console.log('Snuffle snuffle snuffle');
},
},
}
);

Summary

You can run actions on entry to a state, exit from a state, or on a transition. Self-transitions are particularly useful if you want to run an action without leaving the state.

Next, we’ll look at using actions to store arbitrary values in your state machine via context.