Hello XState Part 3: Writing my first state machines (and washing my hands)
Eka
Posted on July 21, 2020
Previously in this series, we went through the default statechart / state machine example in XState Viz.
Hello XState Part 2: Exploring the XState Viz example
Eka ・ Jul 13 ・ 5 min read
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.
- 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.
- 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 thewashed
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.
- We now have both the
STOP_WASH
event that we can run anytime, which transitions to theunwashed
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.
- Everything other than
washing
is identical to example 2b. - The
washing
state now has two child states (also known as substates):tapOff
andtapOn
. We achieve this by literally nesting a states object—in this casetapStates
—inside thestates.washing
property.-
washing
is called a composite state because it has child states, whileunwashed
andwashed
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.
-
unwashed
andwashed
states are unchanged. -
tapStates
are unchanged, except I renamedTURN_ON
toTAP_ON
(and its corresponding “off” state). - In addition to
tapStates
, now we also havesoapStates
which defines the soap dispenser state. It can be turned on and off with theSOAP_ON
andSOAP_OFF
events. - Then we have
washingSubstates
that contains two substates,tapStates
andsoapStates
. We settype: '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
orwashing.tapOn
-
washing.soapOff
orwashing.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
Posted on July 21, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.