There’s No Such Thing as a Global State

There’s No Such Thing as a Global State

Some time ago in the project I work on we adopted React Query library. After a few months of using it, we've realized we don't have any global UI state.

Let me explain what I mean.

An example

Let's look at an example of a flow you might find in a simple todo application.

  1. Fetch and display a list of todos.
  2. Select todos to mark as finished.
  3. Mark selected todos as finished.
  4. Display updated list of todos.

What data do we need to store here? For sure we need all todos returned from the API. Our state might look something like so.

[
  {
    id: "41c47fe6",
    title: "prepare a draft",
    status: "completed",
  },
  {
    id: "8959e3bc",
    title: "write an article",
    status: "ongoing",
  },
  {
    id: "9093c0fc",
    title: "publish",
    status: "pending",
  },
]

Then we also need to know which items are selected by the user. We might be tempted to reuse the existing data structure and do something like this.

[
  {
    id: "41c47fe6",
    title: "prepare a draft",
    status: "completed",
    selected: false,
  },
  {
    id: "8959e3bc",
    title: "write an article",
    status: "pending",
    selected: true,
  },
  {
    id: "9093c0fc",
    title: "publish",
    status: "pending",
    selected: true,
  },
]

Seems harmless, right?

But what happens if we want to refresh this list? If we want to maintain the current item selection, we cannot just replace the existing state with new data from the server. We need to create some logic that will merge the existing state with the new one. As the app further evolves, that can get messy fast.

Also, what happens when we need to implement a new widget in our application that also needs todos data? If we move the state as is to a global state (e.g. managed with Redux) all of a sudden item selection action becomes global as well, whether it needs to be or not. This way, over time more and more elements of state will end up being globally accessible.

Two types of state

What's the reason for all of that?

We're mixing two different types of state - server cache and UI state. Look, the list of todos is not a true UI state. It's just a snapshot of the data available on the server at a certain point in time. It's just a server cache. The only UI state that we have is the information about which items are currently selected by the user (this information we most probably don't need to persist across multiple sessions).

Kent C. Dodds once again has a great article on this.

Server cache is not the same as UI state and should be handled differently.

Separating these two is key to massively simplifying state management.

What this leads to

So how should we approach our todo example? First, let's leave the server cache as it is and store selected item ids as an array.

["8959e3bc", "9093c0fc"]

We just separated the server cache from the UI state. We can now move them independently and update one regardless of the other if we need to.

Handling server cache with React Query

React Query turbocharges handling the server cache. It makes it easy to access the same server data in different places of the app, describes when the data should be updated, and, most importantly, makes it hard to mix the server cache with the UI state.

Going back to our todo example, here's some data that qualifies as server cache:

  • list of todos
  • logged in user data
  • user preferences
  • dynamic application configuration

Here are some examples of UI state:

  • currently selected todos
  • dropdown menu opened
  • current color theme (although sometimes you might want to persist this information on the server as well)

That approach made us see that most of the state in our app is actually server cache, and most of the remaining UI state is local.

What if I really have a global state

I know, not all apps are created equal. There might be a case where you need to share some UI state across multiple components, scattered all over the application.

Before jumping to Redux, consider if you really need it. React is already a state management library. With React.Context you can create multiple state containers, allowing you to colocate and break the global state into smaller pieces instead of having to keep all the data in a single place.

Think of the global state the same way as you think about the local state, only available in the entire app.

Conclusion

Separating server cache from UI state made us realize that a rich web application, featuring a couple of dozen views, used by hundreds of thousands of daily users doesn't have any global UI state. There's a high chance your app doesn't actually have one either.

Whenever you have some state, ask the following question.

Is this data a server cache or UI state? If the latter, what parts of my app need this data?

I hope you found it useful. Good luck!

PS. React Query is mind-blowingly awesome. Go try it out!

Further reading and references

 
Share this