How we used state machines to improve state management

Chirag Patel
4 min readApr 10, 2021

State management is hard. When the app state becomes unpredictable it leads to all kinds of UI errors. But it doesn’t have to be this way. In this post, we’ll see how to solve this problem using state machines.

The Problem with state management

UNCERTAIN CHANGES IN STATE

Redux is a very good option for managing state, but in redux, we can dispatch any action at any time. Redux is not opinionated about should be the end result of those actions. Its job is just to handle state operations in an orderly synchronous fashion.

In my one project, I faced an issue with my payment module was for creating a cart. When users select membership we create a cart and for that, we dispatch cart_create action. Ideally, you would only dispatch cart_create action once. But what If for some bug in your code, we dispatch this action twice. Because redux is not opinionated about the end state it will create duplicate carts.

And to our surprise, we saw a rise in our cart creation numbers but on the other hand activation number of the order were the same :D

The typical way to handle these kinds of issues in redux is to add a check condition for the current state.

But if we use state machines, this kind of state management issue will never happen in the first place.

How State Machine can help?

A state machine is a mathematical model of computation. It is an abstract machine that can be in exactly one of a finite number of states at any given time.

This means that all the possible states of machines will have to be pre-defined. And because we would never define a state where there is a possibility of duplicate states like duplicate carts.

This makes state management easy and in turn, keeps UI more predictable. It makes UI easy to debug and results in fewer bugs overall.

How State Machine Work?

To understand how the state machine works. Let’s discuss a simple Login Form Application with “username” and “password” fields. Users fill this field and then redirect to the authenticated page.

So before defining a state for that first we define the process of that.

  • We display the form with the respective field
  • User fill the field and click on Login Button
  • Call API for authentication
  • parse the retrieved data and redirect to the user on the authenticated page
  • If error then displays an error message and again shows the “Login Button” and on trigger the same process start.

So now we are thinking about the State of this process How many states we have for this process and what is the input for that process?

Idle

In this state, we display a login form to the user and wait for the user interaction.

SubmitButton :
when users fill the form and press the submit button we call the API and transition to the machine on the LoginProcess state.

LoginProcess

In this state two actions present :

Success :
If a response with success status and data then we redirect to the user

Failure :
If a response has some error then transition to the machine on the Error state.

Error

In this, we display the error message and show again login button and have one action

retry :
when again click on the button we start the same process and transition to the machine on the LoginProcess state.

login form state machine

Here the simple graphical representation of the above process.

Now to create a machine for this representation.

const machine = {
state: 'idle',
transitions: {
'idle': {
submitButton: function() {
changeState("LoginProcess");
const response = authAPI.data().then();
try {
dispatch('success');
}
catch(error) {
dispatch('failure', error);
}
}
},
'LoginProcess': {
success: function() {
redirectTOAuthenticatedPage();
},
failure: function() {
changeState("error");
}
},
'error': {
'retry': function() {
changeState("idle");
dispatch("submitButton");
}
}
}
}
machine.dispatch("submitButton");

So in the above code first our machine will be in an Idle state. When users click on the button first we change the machine state and then call the API. Base on the API response we dispatch the success and failure state.

On success, we redirect the user. On failure, we change our machine state. On error state, we will retry action in which we dispatch the submit button.

So, that’s how we can implement state machine without the use of any third party library
but you could also use a library like XState for this purpose.

So by using this approach we solve uncertain changes in the state which I explained earlier because the state machine can only dispatch those events which are presenting in the current state. If the machine in an idle state then it cannot dispatch the “retry” event and result in an undesired state.

Also, the machine is itself acts as documentation for UI flow. If you read all the states in a state machine and get the Idea about the entire UI flow and its states.

Conclusion:

Thinking of UI in terms of a finite number of states helps us pre-define our states using the state machines. It makes UI more deterministic. State management easy to handle and maintain.

Reference :

https://xstate.js.org/docs/
https://gitter.im/statecharts/statecharts
https://www.youtube.com/watch?v=czi24DqUfSA&t=144s

--

--

Chirag Patel

Software developer, Love Programming, Work in React, Redux, Node.