When building user interface, the question of ‘state’ will quickly surface. Simple interactions usually require simple state changes. Something is on or it is off. It is open or closed. However, for anything sufficiently more complex things quickly become difficult. In my experience, an increased number of states for any given section of UI creates a number of problems that increase exponentially rather than grow linearly.

This is the point at which I find great utility in Finite State Machines.

Here’s my own “York Notes” explanation of Finite State Machines; it is a way of describing distinct states of UI as well as defining the allowable paths/transitions from one state to another.

In practical terms, a finite state machine provides a pattern to ensure everything that should occur in one state stays in that state and doesn’t leak across to another. For example, it is a coding pattern that stops you coding oddities in your UI where a logged in avatar is shown when a log in process has failed. The states in your UI are finite so you never get mish-mashes you don’t want.

I’ll write down the pattern I use, much for my own posterity but there are many variations on the theme. I certainly didn’t come up with this pattern, I lifted it from somewhere, I just don’t remember where! If anyone knows, let me know and I’ll link it up.

Resources on Finite State Machines

Also, if you are looking for a more thorough dive into Finite State Machines:

A basic JavaSript pattern for Finite State Machines

My needs are typically pedestrian. As such, my pattern is very basic. However, if you have never used Finite State Machines before, I dare say it would be a good place to ‘dip you toe in’.

The first step in creating a Finite State Machine (here after referred to as FSM), is to draw the state machine. If you think you need to be able to draw well, don’t be alarmed. If you can draw rough boxes and lines you command all the faculties necessary.

Let’s imagine a simple scenario we might want a FSM for: a Logging In process. We want to make allowance for four possible states:

  1. Not logged in
  2. Processing a log in
  3. Log in fail
  4. Logged in

There will be a number of actions and transitions between these states.

A rough diagram of the requirements as a FSM

Here is my rudimentary sketch of the FSM:

Basic finite state diagram

The states are the squares and the lines are the actions/transitions between the states. Let’s build this in code. The pattern requires three distinct sections. The first is called machineInput here. It’s the part that takes some input and moves the FSM to the next appropriate state. You might prefer to name it to something that better suits your own mental model, ‘machineStateSetter’ or similar for example.

The machine that sets the state

This section of code is the same for me every time. It looks like this:

// machine state setter
const machineInput = function(name) {
    const state = machine.currentState;
    if (machine.states[state][name]) {
        machine.currentState = machine.states[state][name];
    }
    console.log(`${state} + ${name} -> ${machine.currentState}`);
    renderUi(machine.currentState);
};

To re-iterate, you don’t need to touch the machineInput function. You just send a transition in and it changes the machine state to the appropriate state. Before we do that we need to define our finite states and the transitions between them.

Defining the possible states of our machine and their transitions

So, the second section is where we define the states and transitions of our machine. Again, you may wish to rename the function to something that better suits your mental model.

Here we are not writing any logic of actions. We are just describing as an object each state of the machine, what input that state accepts and what the machine should move the state to when it receives that input.

var machine = {
    currentState: "notLoggedIn",
    states: {
        notLoggedIn: {
            submit: "processing",
        },
        processing: {
            problem: "failed",
            success: "loggedIn",
        },
        failed: {
            submit: "processing",
            cancel: "notLoggedIn",
        },
        loggedIn: {
            logout: "notLoggedIn",
        },
    },
};

currentState describes the opening state for your FSM. Then for example, if you are not logged in and you click submit, you move to the ‘processing’ state.

Reacting to the state change

The final piece of the puzzle is reacting to the current state of the FSM. That is where the renderUi function comes in. Each time you run the machineInput function, after updating the machine state, we want to update the UI. You could use different methods of control flow but the example I see implemented most often is a switch statement. For example:

function renderUi(state) {
    let willOrWont = [true, true, true, true, false];
    let rand = willOrWont[Math.floor(Math.random() * willOrWont.length)];
    switch (state) {
        case "loggedIn":
            root.setAttribute("data-fsm-state", machine.currentState);
            break;
        case "processing":
            root.setAttribute("data-fsm-state", machine.currentState);
            break;
        case "failed":
            root.setAttribute("data-fsm-state", machine.currentState);
            setTimeout(() => {
                if (rand) {
                    machineInput("success");
                } else {
                    machineInput("fail");
                }
            }, 2000);
            break;
        case "loggedIn":
            root.setAttribute("data-fsm-state", machine.currentState);
            break;
    }
}

Each case in the switch statement represents one of the boxes in our initial drawing. Everything that needs to happen for a given state should be enclosed in the relevant case section. This absolutely guarantees that only what is intended (or at least defined!) for each state occurs.

All other interactions in the DOM merely send something into machineInput and that then, based on what you have defined either moves things on to the relevant state or, if nothing is defined, does nothing.

Summary

It feels a little soon to be writing a summary but the truth is, for a beginners introduction, there isn’t much more to say. I really like the FSM pattern. I don’t use it for every little piece of interaction but I’ve gotten better at judging when something is getting sufficiently complex to warrant one. I also find it is a pattern that lends itself to easier trouble-shooting down the line.

In short, I use it often and find myself reaching for it more and more. I’ve often heard people tell me it’s nothing more than a switch statement, which is, in a reductionist sense, largely true. However, I think that denigrates the predictability that it forces, not just in code but in your thinking about the code.

Anyway, give it a go, let me know how you find it.