Łukasz Makuch

Łukasz Makuch

State management in JavaScript: data-related state and behavior-related state

Data-related state and behavior-related state

State management is a hot topic in the JavaScript world.

Some libraries promote the use of immutable data structures, while other allow mutations.

Sometimes the whole state is stored in one place, other times it's distributed among many objects.

Not every state container is about pulling things out of it. There are also Hollywood-like (push) solutions which actually call us.

We have various ways to transform events into state changes as well.

And what's nice is that it's not just a theoretical thing. It's not uncommon to find at least few implementations of each combination of these characteristics!

This wide choice of state management techniques gives us the possibility to pick the one we feel comfortable with. If there's a tool which helps us turn business requirements into working software without too much struggle, it's a good tool. It doesn't necessary need to be the exact same thing other people use.

I do really love the richness of the JavaScript ecosystem!

However, all the popular state management solutions I know about, in all their richness, focus solely on dealing with key-value pairs.

Let's have a quick look at some key-value pairs. I wrote them on a whiteboard, because the whiteboard doesn't put many restrictions on the syntax.

Data-related state on a whiteboard

Making it part of a JavaScript program isn't hard.

{
  name: 'Łukasz',
  favDish: 'pizza'
}

That's it! Isn't it a wonderful example of a readable, easy to understand part of a program?

I have that feeling that due to their commonness key-value pairs may be underappreciated. All we need to do to leverage their power is to write a bunch of values with their corresponding keys and then refer to them using those keys.

Let's say there's a person. Discovering the favorite dish is that simple:

const favDish = person.favDish;

There's no need to work with pointers, not even numeric indexes! The beginning and the end of data is held under the hood in such a convenient way that in the vast majority of cases we don't even need to think where exactly the actual data ends and garbage memory starts. So many hardware-related aspects of data access are abstracted away! The actual programming looks very much like taking human-readable notes on a whiteboard!

It all makes key-value pairs a model suitable for data-related state, that is state which may usually be consumed without using conditionals. As we can see, it plays well with other declarative techniques such as JSX.

<p>My name is {person.name} and my favorite dish is {person.favDish}.</p>

Even though I do really enjoy using key-value pairs to model data, I must admit that every time I try to use them to describe changes of behavior, they make me struggle.

In order to illustrate my concerns, I'd like show you yet another whiteboard drawing. In this picture I captured all the main screens of a wizard-like application as well as the events which make one screen change to another. To put it another way, it's about behavior-related state.

Behavior-related state on a whiteboard

It's nothing but a directed graph.

An attempt to express it with key-value pairs shows that the graph is completely lost.

//...
const someState = {
  //...
  isABunnyOwner: true,
  enteredTheWholeName: true,
  hasACarrot: false,
  finishedFeeding: true
};
//...
const repeat = (prevState) => ({
  //...
  isABunnyOwner: undefined,
  enteredTheWholeName: false,
  hasACarrot: prevState.hasACarrot,
  finishedFeeding: false,
});
//...
if (isABunnyOwner && enteredTheWholeName && !finishedFeeding) {
  //...
}
//...

What we can see here are not boxes and arrows, but boolean flags and IF statements.

Listing all the possible screens or discovering how the user may go from one screen to another becomes a challenge. It's not so easy anymore like it was when key-value pairs were used to model just data. Now there are boolean flags and IFs to think about.

Some of these problems may be mitigated by extracting functions and grouping them together so they are easier to find. But is it the way to go, or maybe just an attempt to bend an inappropriate model to our needs?

They say that if all you have is a hammer, everything looks like a nail. It seems that it applies to state management as well. If all we have are key-value pairs, everything looks like a value or a key.

Let's step back and try to find a more suitable model. How can we make the drawing visible below part of the program?

Behavior-related state on a whiteboard

What if we could just open an editor and draw it like this? Behavior-related state on a whiteboard

The code could then forget about the past. There would be no boolean flags at all! IF statements would be unnecessary. This approach could make the complicated code we've seen totally disappear.

Well, it's actually possible! A directed graph like that is a representation of a finite automaton, that is one of the fundamental concepts in computer science. Method calls may be dispatched based upon the current state of the state machine. Thanks to the development of Graphical User Interfaces, we're not tied to text-based interfaces. We can simply draw our graph using a visual editor. This is how we get into the world of visual automata-based programming, where drawings of graphs are not less important than key-value pairs.

The editor screenshot visible above comes from an article about React and visual automata-based programming.

From the author of this blog

  • howlong.app - a timesheet built for freelancers, not against them!