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.
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:
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.
import { createMachine } from 'xstate';
const machine = createMachine(
{
entry: ['iSayHello', 'youSayGoodbye'],
},
{
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.
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.
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.