State management in React upgraded: xstate
Introduction (the finite state machine concept inside React)
Finite state machines are concept that’s been around for a rather long time and has nothing in particular to do with React. I won’t go all wikipedia on you, but the main idea is that you have a finite set of states, and your app can be in one state, and one state only, in any one point in time. Provided with a certain input, the finite state machine transitions to another one of it’s states. Now this bit of distilled computer science sounds a lot like state management with an immutable single source of truth you’ve no doubt heard so much about.
What the xstate
library does, is that it provides you with your very own set of utilities to create finite state machines (FSM) to manage your javascript application state. In itself, xstate
isn’t biased towards any one popular library/framework — it’s just a javascript implementation of a concept, and as of now is available as a package for React, Vue and Svelte.
My immediate concern was sharing a state machine between components. Right now, the best way to share state machines between components is by use of React’s built in useContext
hook.
If your’re somewhat familiar with xstate
or prefer to go at your own pace feel free to checkout the github repository I used to try everything out. It has a test for the state machine and the implementation of useContext
both of which are NOT covered in the content below.
The implementation
What I personally like about switching to FSM managed state is the ease of implementation. Exhibit A — this is the weight of your state machine dependencies:
Once you’ve planned out your state machine on a piece of paper like a real computer scientist, you can start writing your very first FSM in xstate
. By the way, to get the idea of how to draw state machines on paper and impress your friends, checkout this handy tool you’ll be able to use to analyze your implemented FSMs. But more on that later.
This is the basis of a FSM written with xstate
.
This particular FSM deals with fetching a set of data, you’ll see that it has
- a human readable
id
, - we set the
initial
state right away, - it has the so called
context
(which is what you’ll be able to manipulate in yourstates
section), - and a section detailing the
states
of your state machine.
That’s all there is to it. The states
section is completely under your control — the state names provided in this example are entirely up to you to name — there is no framework-like constraint. Also, there’s no ugly switch case statement so common in these types of state management APIs. Everything is neatly packaged inside a configuration object.
We see that the initial state is set to idle
. To transition from one state to the other we use (yes, you’ve guessed it) transitions. This is how a transition is implemented.
Both FETCH
and loading
are arbitrary names you chose earlier. How cool is that? So if the state is idle
, if the machine receives a FETCH
command, it will transition to the state called loading
. For each state defined in your states
, you can have any number of on
objects, each being named by a command you intend to send to your FSM and each leading to the next intended state.
Now, when dealing with async code, namely your equivalent of the loading
state in our example, things get ever so slightly more complicated.
The loading
state has an invocation config under the invoke
prop. Simplified and pruned of comments that would look like this.
And the invoke broken down would look like this:
I hope I’ve commented this section of the code clearly, and the collapsed, simplified screenshots of the configuration should highlight key props for configuring an asynchronous data call.
Once your state machine is ready, import it into your component and use it with a useMachine
hook:
That’s more or less the gist of FSMs including some slightly advanced features brought in to accommodate asynchronous code.
But how does it scale?
Well, as the complexity of your app grows we are faced with what is called a “state and transition explosion”. Thankfully, because FSMs as concept have been around since the 40s , this has been dealt with. In 1987, a dude name David Harel established statecharts. This is all detailed in David Kourdish’s talk at the CSSConf BP 2019 — what I’ll try to explain bellow is right there (only put more eloquently by the writer of the xstate
library, himself), so I implore you, if you’ve come this far and have about 40 minutes to spare, do check it out.
So, to keep it short, statecharts conceptually provide:
- ACTIONS (
xstate
has hook-like configuration forentry
andexit
events, transitions are actions by the way, etc) - GUARDS (you can implement conditional transitions)
- HIERARCHY (you can have nested states)
- ORTHOGONALITY (you can have parallel states)
- HISTORY (you can transition back to remembered states)
If you plan to add xstate
to your project, make sure you keep this in mind and find out more in the official docs.
Conclusion
The main advantages of using FSMs and statecharts in your applications are the ability to visualize your software modeling process, to create easy to reason about (and test) precise diagrams of your logic, awesome test coverage, auto generated code and most importantly, with all this, accommodation of late stage breaking requirement changes.
What might hold you back is the learning curve, and the fact that this way of thinking about your software requires planning ahead (but to be honest, not a lot of good projects happen without planning ahead). Also, there is a caveat — not everything can be modeled to use statecharts (yet).
As for myself, I’m fully sold on the idea and look forward to using xstate
the next time an opportunity arises. I also hope I’ve nudged you in the right direction and that the front end dev community will start to be more aware of software modeling with techniques similar to what’s been described here. This article only scratches the surface.
If you think you have some insights into xstate I may have left out or misinterpreted I encourage you to contact me— the subject is broad and can be approached in a myriad of ways. If you want to reach out or just grow your network — you can find me on twitter and linkedin as well.