XState - a Typescript state machine with a lot of features
This post was sparked by a tweet from Ryan Florence…
When I think about building a UI like the checkout process for my workshops it’s impossible to imagine doing it without xstate.
— Ryan Florence (@ryanflorence) January 13, 2019
The visualizer helps me write the code w/o any significant bugs or blindspots from the start.
Great job @DavidKPiano https://t.co/v4zjMtkm15 pic.twitter.com/RnmOTsAe5X
It has been quite some time ago since I last looked at defining a state machine with a DSL in C# and then execute it, so I got curious and visited xstate.
What you get access to is a library that gives you the ability to declare a state machine with a lot of features. I wanted to recreate the “tape player” from my post 12 years ago. Considering that xstate brings a lot more to the plate, I wanted the tape player to be more feature complete than just being able to play and stop. The source code for the final result runs as a react app and can be found here.
At statecharts.github.io it is possible to visualize the state machine that you defined. For the tape player in the source code we get this:
Unfortunately you can’t move the boxes. What you see is the states (the big boxes) and events (the small labels that start arrows) that define your state machine.
The tape player is initially stopped. The PLAY event moves it to playing. Basically you can move from playing to forwarding and back to playing, but if you want to be rewinding, you need to be stopped first.
You perform transitions by raising events. These events can be typed:
Then you can define your available states:
The state machine also allows to define a context. Here we will use it to track the tape position:
These three types give you a little bit of compile-time safety once you start defining the main configuration object for your state machine:
At the top you will find definitions for the initial state and the initial context.
The states
property then defines all states and to which state you will transition given an event.
Then you can use an “onEntry” hook to specify an effect to occur when the state is entered. This is essentially a call into a function. The effects that I have defined change the pos-field of the context.
They move the position slower or faster, depending on when those effects will take place.
The final piece of the puzzle is the possibility to specify state transitions after a specified amount of time.
Here, the rewinding state transitions to itself after 500 milliseconds. If the specified condition is true, it will stay in the rewinding state, otherwise it will move to the stopped state. The effect specified in onEntry
will be called everytime the state is entered.
For some fun, we can then hook the machine to some visuals…
First, you need to create the state machine and then run it through an interpreter:
of interest is all the info that we get during a transition of the state machine. We will get
- the current tape position
- the current state
- and the list of possible next states given the current one.
All this is put into the state of the component.
Finally, on componentDidMount
, we start()
the interpreted machine (returned as service to an instance field of the component within the component’s constructor).
I define two methods that help us check on the current state from within render()
Which can be used as such e.g. on the rewind button:
And as a final example, the rewind click:
This is how you send events to the state machine.
As you can see, xstate was up for the task and I can tell you that I barely scratched the surface of what you can represent with the library. The UI is fully driven by the state machine and the events that you can raise with the buttons. Smooth!
Don’t forget, the full example is available here on the basis of create-react-app. Enjoy your states!