This is the first in a series of technical blog posts contributed by VanHackers. Today’s post is by Carlos G.
You can check out his VanHack profile here – https://vanhack.com/vanhacker/486559.
Let me tell you a story – it might sound familiar to you.
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 with a few tricks because I’m a smart and experienced developer 😉.
Later though, I start feeling that tricks aren’t the best answer. I’m sometimes astonished about how a simple idea I had in my mind, gets so complex and verbose when I map it 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 does not worry me 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. Now my colleague has many questions because he doesn’t know how the heck the app works! and what’s the idea behind it. I care about his health and I take the time to explain the code to him. I’m a kind person and I don’t want to make him waste time 😅.
Sometime later I’m assigned to other app and, right now, I can understand the pain when my colleague was trying to understand my code because I have to figure out how the heck this new UI code works!
Some months later I’m reassigned to the first app for solving a bug, and you know, I have to figure out what I was thinking these back when I coded the tricks!
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.
These problems happen to almost all people involved in any kind of reactive systems development such as UI development. Some people are used to struggling with it, because 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. Solving this problem is a matter of getting things explicit from the beginning. 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 and at the same time preventing unexpected behavior. Just FYI this formalism is used at NASA for the Mars Science Laboratory Mission.
Yep! Curiosity uses statecharts! 😮 😎
You give 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 not 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:
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:
Building a Statechart 🏗
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:
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.
We can make conditional transitions. For example, I want that the user only turns off the machine while it is on the on.green state. For that let’s use the In Guard.
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.