Introduction

A React application architecture for predictable user experiences, by CodeSandbox

At CodeSandbox we are building complex web applications with a lot of client side state management and related logic. Our journey building CodeSandbox has taught us a lot about state management and writing code in general. With an evolving JavaScript ecosystem producing new ideas, and introducing old ideas, we have taken several steps to improve our state management game. We are now taking yet another step, bringing all our learnings a long and sharing with you a React architecture that is the foundation of all the products coming down the pipe. And it is all about two fuzzy terms, explicitness and constraints.

Explicit states is the foundation of how you build explicitness and constraints in your code. To understand this concept look at this typical way of modelling state:

type State = {
  isAuthenticating: boolean,
  user: { name: string } | null,
  error: string | null
}

When we model our state this way we create a layer of interpretation between the state and the components creating the user experience. We can identify this by asking some questions:

  1. When does this state represent the application being AUTHENTICATED?

  2. If we have a user and isAuthenticating is true. Are we AUTHENTICATED, or are we AUTHENTICATING?

  3. If we have an error and isAuthenticating is true. Are we in an ERROR state or are we AUTHENTICATING?

There are actually 8 variations of how this state can be consumed.

It might seem unlikely that you would actually consume this state wrong, and you would be right about that. More often than not we get this right, but there is a fundamental fragility. And this fragility increases with the complexity of the state model, the number of components consuming the state, the number of people working on the code and the number of changes that is being made to the code in the future. By applying explicitness and constraints we reduce this fragility.

If we rather thought about our state like this:

type State =
  | {
      state: 'UNAUTHENTICATED'
    }
  | {
      state: 'AUTHENTICATING'
    }
  | {
      state: 'AUTHENTICATED'
      user: { name: string }
    }
  | {
      state: 'ERROR'
      error: string
    }

Now there is absolutely no question about what authentication state we are in, and there are only 4 variations. This simple change of approach does not only remove this layer of interpretation causing bugs and inconsistencies, but it lays the whole foundation of how we can create explicitness and constraints through the whole application to produce a predictable user experience.

To get more insight into why explicit states matter, please watch this video:

Last updated

Was this helpful?