Hello XState Part 3: Writing my first state machines (and washing my hands)

ekafyi

Eka

Posted on July 21, 2020

Hello XState Part 3: Writing my first state machines (and washing my hands)
Cover image by Wali's Studio for the UN COVID-19 Response

Previously in this series, we went through the default statechart / state machine example in XState Viz.

Now it’s time to write my own. I’m going to make a state machine for something we all do multiple times every day now: washing our hands 👏🏼💧.

Example 1: Just wash your hands

This is the most simple iteration of the “hand washing” story.

visual diagram

  • We begin with the initial state of unwashed.
  • On the START_WASH event, we transition to the next state, washing.
  • On the STOP_WASH event, we transition to the final state, washed.

💻 Try it!

Example 2: Delayed transition

Health experts recommend that we scrub our hands for at least 20 seconds. For the purpose of this example, I am simplifying the 20-second duration into the entire handwashing activity.

XState has a built-in feature for automatic delayed transitions—precisely what we are trying to do here—with the after property.

visual diagram

  • Initial state is unwashed (no change).
  • On the START_WASH event, we transition to the next state, washing (no change).
  • Now instead of the STOP_WASH event, we have a “timer” block that automatically takes us to the washed state after 20 seconds. We can see how a delayed transition is treated as a special type of event here.

💻 Try it! (the timer animation is a nice touch!)

Example 2b: Combining regular and delayed transitions

But what if I stop washing my hands before 20 seconds? Sure, we can allow that, but that should not count as having washed our hands properly. We can accommodate this by combining the regular transition (like in the first example) with the delayed transition.

visual diagram

I mistyped the delay duration; it should be 20000ms instead of 2000 🤦🏽‍♀️
  • We now have both the STOP_WASH event that we can run anytime, which transitions to the unwashed state.
  • Once the timer reaches 20 seconds, we transition to the washed state.

💻 Try it!

Example 3: Hierarchical states

As we discuss the specifications of our hypothetical handwashing app with our hypothetical team, we learn that our application will involve a water tap, which will be either on or off at any given point.

Does the tap’s on state correspond to our current washing state, though? Maybe not. To preserve water, we may (should!) turn the water off while lathering and scrubbing, then turn it back on to rinse. All those are considered within the washing state.

Let’s update our machine accordingly, this time taking advantage of XState’s hierarchical states.

visual diagram

  • Everything other than washing is identical to example 2b.
  • The washing state now has two child states (also known as substates): tapOff and tapOn. We achieve this by literally nesting a states object—in this case tapStates—inside the states.washing property.
    • washing is called a composite state because it has child states, while unwashed and washed are simple states.

💻 Try it!

Example 4: Parallel states

If we’ve got a tap, we must have a soap dispenser as well. Soap can only be dispensed while washing our hands, regardless of whether the water is running or not. This use case can be achieved with XState’s parallel states.

visual diagram

  • unwashed and washed states are unchanged.
  • tapStates are unchanged, except I renamed TURN_ON to TAP_ON (and its corresponding “off” state).
  • In addition to tapStates, now we also have soapStates which defines the soap dispenser state. It can be turned on and off with the SOAP_ON and SOAP_OFF events.
  • Then we have washingSubstates that contains two substates, tapStates and soapStates. We set type: 'parallel' to indicate that they are parallel (also known as orthogonal) nodes.
  • Lastly, we add these substates as a substate of the washing state.

If we look at our code, we appear to have three levels of nested states now. But in reality—as the diagram suggests—we still only have two levels of nested states, with the second/middle substate serving as a “container” of the parallel states.

  • washing
    • washing.tapOff or washing.tapOn
    • washing.soapOff or washing.soapOn

💻 Try it!

Conclusion

The examples above show simple use cases that demonstrates the basics of XState’s API, namely transition delays, hierarchical (nested) states, and parallel (orthogonal) states.. In a real-world implementation, we may need to create more complex statecharts with—among others—conditionals, dynamic delays, and context data.

Creating finite state machines “forces” us to think about the way our application behaves—along with the requirements and implications—and document it before getting to the actual UI code (or the backend / API code for that matter). We trade efforts and dev hours upfront to avoid preventable bugs and wasted hours further down the road as the project and/or the team grows.

From a frontend dev perspective, if your project is handed over to other developers, perhaps to be recreated or incorporated in a different stack, they would not have to comb over multiple useState hooks and nested JSX to find the states logic.

State machines and statecharts can be implemented with no external library or with other libraries, but I prefer XState as it provides handy functionalities for various use cases (like the examples here), which enables me to define my machine states in brief, simple, plain JavaScript objects. Finally, the visual interface of the XState Viz helps us develop, document, and communicate our state machines and statecharts.

Stay tuned for the next post, where I’m going to implement the state machines into the UI code. Take care and wash your hands!


Bonus Track

By now we’ve done enough handwashing to be completely germ-free, so this song is fitting.


References

💖 💪 🙅 🚩
ekafyi
Eka

Posted on July 21, 2020

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

Sign up to receive the latest update from our blog.

Related