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.
Top comments (0)