State machine advent: One event, two possible state transitions (15/24)

codingdive

Mikey Stengel

Posted on December 15, 2019

State machine advent: One event, two possible state transitions (15/24)

Conditional logic is everywhere. While state machines reduce conditional logic by eliminating impossible states, there is some conditional logic we want to have within our machines. In particular, when one or the other action should be executed or multiple state transitions exist. We can define such conditional logic using the very concept we've learned yesterday, guards.

By providing an array of possible state transitions, the state transition with the first guard that evaluates to true will determine the next state of our machine. Let's say we want our thermostat to distinctively express whether it is cold or warm. If the temperature is below 18°C, it should go into the cold state and above, transition to the warm state.

import { Machine, assign } = 'xstate';

const thermostatMachine = Machine({
  id: 'thermostat',
  initial: 'inactive',
  context: {
    temperature: 20,
  },
  states: {
    inactive: {
      on: {
        POWER_TOGGLE: 'active'
      }
    },
    active: {
      initial: 'warm',
      states: {
        cold: {},
        warm: {},
      },
      on: {
        POWER_TOGGLE: {
          target: 'inactive',
        },
        SET_TEMPERATURE: [
            {
              target: '.cold',
              cond: (context, event) => event.temperature < 18,
              actions: assign({
                temperature: (context, event) => event.temperature,
              }),
            },
            {
              // transition without a guard as a fallback.
              target: '.warm',
              actions: assign({
                temperature: (context, event) => event.temperature,
              }),
            },
         ]
      }
    },
  }
});
Enter fullscreen mode Exit fullscreen mode

Think of the state transition array as a switch case to determine the next state of a machine. The default transition can be expressed as a state transition without a guard as seen in the example above.

Notice how we had to duplicate the action to assign the temperature. Similar to when machines transition from one state to another, actions are only executed if no guard is defined, or when a guard evaluates to true.

To give one more example of this behavior, the code below will never call the 'log' action.

[
    {
        target: 'cold',
        cond: () => false,
        actions: 'log',
    },
    {
        target: 'warm',
    },
]
Enter fullscreen mode Exit fullscreen mode

Tomorrow we'll refactor the thermostatMachine so that we don't have to define the same action twice.

About this series

Throughout the first 24 days of December, I'll publish a small blog post each day teaching you about the ins and outs of state machines and statecharts.

The first couple of days will be spend on the fundamentals before we'll progress to more advanced concepts.

💖 💪 🙅 🚩
codingdive
Mikey Stengel

Posted on December 15, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related