An Introduction to Finite State Machines: Simplifying React State Management with State Machines

An Introduction to Finite State Machines: Simplifying React State Management with State Machines

An Introduction to Finite State Machines: Simplifying React State Management with State Machines

As their name suggests, a finite state machine is an abstract machine that can only exist in one of any number of (finite) states at once. Such a machine accepts any number of inputs, and, depending on their sequence, changes its internal state and produces an output.

A common example of a state machine that you’ve likely interacted within the real world is an elevator. It can either be going up or down (never both at once), and will stop in the sequence in which floors were input. Another example is a microwave. If the door is open and you press start, nothing happens. If the microwave is running and someone suddenly opens the door, it switches state from ‘running’ to ‘stopped’. Such a change in state is referred to as a transition.

But how does that relate to code? Let’s start with an example of how not to manage React state before we move on to demonstrate how useful state machines can be for simplifying your code.

How to poorly manage React state

One of the most common use-cases on the web that can benefit from the use of state machines is forms. Typically, forms exist in three different states: ‘submitting’, ‘error’, and ‘success’.

Let’s use the following simple form, which allows a user to input the number of car parts they want to order from their favorite automotive company.

As far as code goes, that’s not too bad.

We handle each of the three form states using booleans. Since we’re using Bulma for this project, it will handle hiding the button text and showing a spinner when isLoading is true. When our fake request is completed, it results in either an error or a success result, which is stored in state. In fact, we might be tempted to pat ourselves for a good job done, commit the code and create a pull request to merge our code to master. But let’s not get ahead of ourselves. There’s a good chance any experienced developer will smell the bad code we’ve written here from a mile away.

In computer science, there’s a phenomenon referred to as combinatorial explosion, but since we’re using booleans to handle our state, our problem is referred to as boolean explosion. The boolean explosion is the rapid increase of the complexity of a problem due to the addition of new boolean parameters/states. It happens at the rate of 2^n, where n is the number of states.

The maths works itself out like so

  • 1 boolean => 2^1 = 2 states
  • 2 booleans => 2^2 = 4 states
  • 3 booleans => 2^3 = 8 states
  • 4 booleans => 2^4 = 16 states

But the code above has just 3 states, not 8. This is owed to the fact that there are plenty of impossible states in our code that would be a pain to maintain or refactor later on. For instance, the ‘error’ and ‘success’ state will never exist together, so there’s definitely room to improve our code.

Using state machines to simplify React state

As mentioned above, finite state machines exist in a known number of states, and can only transition in a specific order. For instance, it doesn’t make sense for a form to move from an ‘error’ state to a ‘success’ state or vice versa. The order has to be ‘idle’ -> ‘submitting’ then either ‘success’ or ‘error’.

Therefore, we need to define the logic that will allow us to:

  1. Get rid of impossible states
  2. Define strict transitions in which our state is allowed to move.

To achieve this, we will start by enumerating (manually defining) the list of all possible states and their transitions:

We also need a function that will change the current state to whatever relevant state is next when we pass it an action.

The transition function receives the current state together with the action that we want to be performed. For instance, if we pass the parameters ‘idle’ and ‘submit’, the next state will always be ‘loading’. This will then be the new currentState. Additionally, if anyone passes an invalid action, we simply return the current state.

And, with that, we can update our code:

So far, we have managed to get rid of a ton of complexity, but we’re not there yet. We have reduced the number of states we track from 8 to just one. But our solution still isn’t ideal.

Say we wanted to add a new state to our form. We would first have to add it to our states object then add it to the transitions object together with its corresponding transitions. This same kind of syncing would also happen if we wanted to rename one of the state objects. To rename isLoading to isSubmitting, for example, we would have to rename it in two places, which, again, makes refactoring a pain in the butt.

It’s even worse if you consider the implication of implementing a different component using state machines. You would have to create separate transitions for that component as well, then keep it in sync with its own state enum, which isn’t ideal.

Instead, let’s move on to the final part of our journey with state machines.

Using XState to manage finite state machines

XState is a library that encapsulates all the logic for creating, interpreting, and executing finite state machines. It can also generate state machine diagrams to help you visualize how your program is going to run. To install it, run:

First, let’s define all the states the form is going to exist in:

Next, we define all the transitions we want our form to exist in

This looks a lot like the function we wrote before and reads just the same. From the idle state, we transition to the isLoading state when a SUBMIT_FORM action is called. From the isLoading state, we transition to either isSuccess or isError depending on the action supplied to the machine, and so on.

All that’s left is to hook up the machine to XState.

currentState is an object provided by XState that gives us access to a .matches, which allows us to check for the current internal state of the machine. send is a function that accepts the action that we want to be executed. It’s used like so:

With that, here is the final result:

In this article, we’ve covered a simple use-case for managing React state with state machines.

More complicated applications might leverage some of the XState library’s more advanced functionality. You can have machines communicate with each other and invoke callbacks or promises in response to different events being completed. Since this was meant to be an introductory article, it hasn’t covered those use cases, but the XState documentation does a good job of covering a lot of different applications.

Conclusion

Managing state in React isn’t always straightforward, especially when using react hooks. State machines help to take a lot of complexity out of the equation by defining all possible states a component can be in, eliminating impossible states and preventing boolean explosions in the process.

They can be used to define behaviour for pretty much every kind of component – modals, forms, menus, buttons… e.t.c. Using XState, you can also visualize your component in diagram form. This allows you to map out your whole component before you even write a line of code.

About the author

Stay Informed

It's important to keep up
with industry - subscribe!

Stay Informed

Looks good!
Please enter the correct name.
Please enter the correct email.
Looks good!

Related articles

15.03.2024

JAMstack Architecture with Next.js

The Jamstack architecture, a term coined by Mathias Biilmann, the co-founder of Netlify, encompasses a set of structural practices that rely on ...

Rendering Patterns: Static and Dynamic Rendering in Nextjs

Next.js is popular for its seamless support of static site generation (SSG) and server-side rendering (SSR), which offers developers the flexibility ...

Handling Mutations and Data Fetching Using React Query

Utilizing React Query for data fetching is simple and effective. By removing the complexity of caching, background updates, and error handling, React ...

No comments yet

Sign in

Forgot password?

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy

Password recovery

You can also try to

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy