DEV Community

Carlos Galarza
Carlos Galarza

Posted on

Tackling UI complexity with State Machines

A simple on-off state machine

I’ll tell you my story, maybe is yours too …

So it’s a great day at my workplace and I start coding an app that seems pretty simple.

Time passes, I get some tasks done and, at some point, the application starts getting messy.

I solve the problem by making some tricks because I’m quite smart and experienced developer 😉.

But, I start feeling dirty and sometimes astonished about how that simple idea, I had in my mind, is getting so complex and verbose to map to code. I think there is no one-to-one relationship between my mindmap and the code, it seems like a one-to-many relationship.

I’m the creator. I know how things work and this is not worrying me so much, because I do my best to write understandable and readable code.

After that, the app starts getting bigger and now I have to work with a colleague to speed things up.

Right now my colleague has many questions because he doesn’t know how the f*ck the app works! and what’s the idea behind it. I care about his health and I take the time to explain him the code. I’m a kind person I don’t want to make him waste that time 😅.

Sometime later I’m assigned to other app and, right now, I can understand the pain it was to my dear colleague to understand my code because I have to figure out how the cr*p this new UI code works.

Some months ago I’m reassigned to the first app for solving a bug, and you know, I should figure out how was my thinking these days.

Batman interesting meme

I always thought that there is something I’m missing, some solution that doesn’t come with extended and outdated UI specification documents. Something that makes me and others catch the idea faster and spend less time understanding the code. And yep, I was right, finally, I found it.

Introducing Statecharts

These problems happen to almost all people involved in any kind of reactive systems development such as UI development and, some people are too used to struggle with it, that it seems unavoidable and natural, but it is not.

The problem is a well known one, reactive systems are complex because of event orchestration complexity. And, solving this problem is a matter of getting things explicit from the beginning. So, your mental model should be coded in a way that you and others can easily reason about.

In simple terms, a Statechart is a pure function that contains all the logic related to the event-state orchestration in an explicit way. This way we can avoid the most common bugs and, if there are some, it’s easy to find them.

With statecharts, you have a simple and manageable way to organize this logic by using a graph and some constructions. This gives us the power to reason about complex UI flows at the same time as prevents unexpected behavior to happen. Just FYI this formalism is used at NASA for the Mars Science Laboratory Mission.

Taken from the NASA gallery

Yep! Curiosity uses statecharts! 😮 😎

state, actions = Statechart(state, event, data?)

You give to the statechart the current state, the event that happened and optionally some external data, and it gives you back the next state and the actions that should be performed. Actions is a pretty simple concept, you can understand it as commands or functions that should be executed in response to the happened-event.

You already have state machines in your code

So, you already have this logic in your code but, the problem is that it is no explicit. Here I have to quote Reginald Braithwaite:

*Any sufficiently complicated model class contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of a state machine. — Reginald Braithwaite in How I Learned to Stop Worrying and ❤️ the State Machine*.

You already implement all kind of flows in your UI code, and probably you do it by manually handling all the cases and figuring out the edge cases by doing some exploratory testing. This is known as the bottom-up approach and it is the most common approach to developing UIs these days. We attach event listeners to UI components and perform some kind of action inside it. For instance, in vanilla JS you can have:

const signUpButton = document.getElementById(signUpButton)
signUpButton.addEventListener(click, () => {
  // mutate state and/or perform side effects
})

But what about Redux? Well, in Redux-like architectures you split the event handlers in reducers, for state management, and effect handlers. This is a great practice and Redux solves the state management problem, but you still have to struggle with the event orchestration one.

A matter of paradigms

The above approaches are known as the event-action paradigm. In this paradigm, when some event happens you fire an action in response to it. The problem with it is that, in order to coordinate the UI, you have to put a lot of conditions inside your event handler and even worse you may have nested if-else conditionals, switch cases and so on.

In Redux you split this up in the reducer and the effect handlers but you still have the same problem. This paradigm makes your code complex and bug-prone. You as a developer have to maintain all those cases in your mind and be careful to not forget something.

Statecharts use the event-state-action paradigm which is the nature of complex UIs. And you may say, what’s the difference between statecharts and state diagrams. Long story short:

statecharts = state diagram + nesting + parallelism + broadcast communication

Building a Statechart 🏗

There is a notation for building statecharts using XML called SCXML. We are going to use XState, a JavaScript library which has its own notation but implements most of the things of the SCXML spec. Let’s hands-on building our first statechart.

State diagram

A state diagram is built of nodes and edges. A node represents one of the possible states of the machine and, an edge represents a transition between states. Transitions are dispatched in response to events and, very often those are user events.

So let’s implement a simple on-off statechart:

Nested states

Let’s make some nested states. Suppose that when our UI is in the on state traffic lights are visible and the user can click on them.

Conditions

We can make conditional transitions. For example, I want that the user only turn off the machine while it is on the on.green state. For that lets use the In Guard.

Conclusion

You may be wondering, but in which cases do I need it? Well, let’s start using it for widgets and stateful components.

So, when you have to orchestrate events (state + network requests + user interaction + anything), remember the Statechart is your friend.

What’s next

Next article I ‘ll show how to create a real-world app as well as explaining some good practices and tips for building statecharts for your apps as well as other Statechart features.

Thanks to SchoolApply and especially to Mikael Karon who encourage me to learn and discover the Statecharts world.

Top comments (0)