When actions fire in xstate
Michal Bryxí
Posted on November 13, 2020
While working with statecharts in the popular xstate library I fell into a trap of not understanding how and when are actions fired.
Most of the time it does not really matter. Until it does. That until happened when I tried to "smartly" use transient transitions.
Given following statechart:
const machine = Machine(
{
initial: 'idle',
context: {
winning: 'heads',
selected: 'tails'
},
states: {
idle: {
on: {
SELECT: [
{ target: 'playing' }
]
},
},
playing: {
// It might seem that entry level action fires first.
entry: ['flipCoin'],
on: {
'': [
// And *after* that the guard is checked.
// But this is not how it works.
{ target: 'score', cond: 'isScore' },
{ target: 'nope' }
]
}
},
nope: {
type: 'final'
},
score: {
type: 'final'
},
}
}, {
guards: {
isScore(context) {
console.log('guard isScore');
return context.selected === context.winning;
},
},
actions: {
flipCoin(context) {
context.selected = 'heads';
console.log('action flipCoin');
}
}
}
);
one might assume that given the entry
action called flipCoin
, which will change state selected='heads'
the guard isScore
will return true
and thus we will end in the score
final state.
But this is not how things work. According to the documentation:
Actions are not immediately triggered. Instead, the State object returned from machine.transition(...) will declaratively provide an array of .actions that an interpreter can then execute.
So in my head:
After event is sent to xstate, the next state of the statechart is determined based on current context before any actions are fired.
Posted on November 13, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.