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)