Break up ReactJS application State into Types


Borislav Hadzhiev

5 min


Photo from Unsplash

# The Problem

Years ago I used global state management libraries like redux to handle most (if not all) of the state in my application. I used to put all kinds of state in the redux store - i.e.:

  • form input values (the performance was terrible)
  • modal state
  • server state (async data fetched from the server)
  • currently authenticated user
  • ... everything else

Obviously, I was using redux wrong. I wasn't supposed to put form state or other local state into the global redux store, but I didn't know at the time.

It was way more difficult back then to manage state without the ease of abstracting logic and the code reusability that hooks bring.

It kind of made sense to just put everything in the global state. I didn't have to think about it - I could just access the state from anywhere inside of my application, I didn't have to prop drill or lift the state up to a common parent.

However, the way I was using redux came with some drawbacks too - mainly lots of boilerplate, indirection, the complexity grew as the application grew.

Most of the state I was pretending to be global was not actually global. In fact, in most applications the truly global state is so little that it would be simpler to manage it using React context.

# Types of States in a React.js Application

State is data that changes over time.

The user has control over the data that changes in the client state (i.e. language preference, theme preference, modal state, side menu state, form input values), but they don't have control over the async data on the server (which can be changed by multiple users at the same time).

We can separate state into 2 main types: Server State and Client State.

# Server State

The server state is asynchronous state that's stored on the server (i.e. products, orders) - we cache it on the client for performance reasons. It decreases the load on our database and the users have quicker response times and see fewer loading spinners.

If the state is stored on the server it's not client state, it's server state. This is data that the end user doesn't own and control (can be changed by multiple users at the same time).

# Client State

The client state is synchronous state managed in the browser, lost on page reload and lives in memory.

It is data that the user has control over and it can only be changed by the user locally.

If the state is only maintained on the frontend, it's client state.

Client state can be further separated into Local Client State and Global Client State:

# Local Client State

Local state is UI state that is used in a single or few components that are colocated (they're not spread in completely different places throughout your application), i.e.:

  • modal state
  • form input values
  • style changes local to the rendered page, i.e. visited links are displayed with green color, bolded
  • active nav link
  • current pagination page

# Global Client State

Global state is used for data accessed from different parts of your application and kept in sync between the different components that access it.

It would be a hassle to initialize a language in your App component and then pass the language all throughout your application, this is where the global client state comes in.

This data doesn't come from the server. A common exception is the current authenticated user, which is often stored in the global state(it's not very common that you perform the full CRUD (Create, Read, Updated, Delete) cycle on users).

Examples of Global Client state are:

  • theme preferences

  • locale (i.e. language)

  • current authenticated user - it's often reused in many places in the application, i.e. the navbar or in a user dashboard.

    It's used in components every time you have to conditionally show content based on auth state, i.e. authenticated users can see one type of content and admins and anonymous users can see a different type.

# Why not all state is Global

# Local Client state in Redux Store ❌️

The boilerplate and complexity you'd have to add to your component to manage local state in your global state, paired with the fact you're not actually getting anything out of it, makes for an easy decision.

Just leverage React's default state management hooks - useState and useReducer for managing local client state.

# Server State in Redux Store ❌️

When you keep server state (async data from the server) in the global state, you have to expect that this server state can change because it can be mutated by multiple users at the same time.

Once changed your global state would be out of sync with the server, so it's not the same neither is it as simple as the isDarkMode boolean global state, which can only be changed by the user currently browsing your site.

That adds a lot of complexity and things you have to think about, i.e.:

  • caching
  • deduping requests (multiple requests going out at the same time)
  • mutating the data and keeping it in sync with the server
  • syncing data on different events - i.e. network connection loss, window refocus
  • interval refetching
  • prefetching data
  • ... many more

As a developer I want to leverage a package that will solve these problems for me, so I don't have to implement a solution myself. Right now there are 2 main libraries in the react ecosystem - react-query and swr. My personal preference is react-query, for reasons I'm not going to get into right now, but you can check out this comparison table.

Until recently you had two options for managing the server state:

  • use a global state management library (i.e. redux)
  • manage the state yourself, creating a net of indirection and complexity

Both solutions introduce a lot of complexity to any application that relies on server state and is not super small.

Both solutions don't help you implement any of the optimizations mentioned above.

# Global Client State in Redux Store ❓️

Do you have enough global state that you can't intuitively manage using react context. For most applications, the answer will be no. Unless you have some very complex analytics dashboards, etc, the truly global state in your application could be comfortably kept in react context.

# Outro

You have to use the right tool for the job, i.e. redux if you have lots of truly global state that you couldn't easily manage with context (which is very rare), use react-query for your server-side state, and manage your local component state with useState and useReducer.

# Resources:

Check out these talks from Tanner Linsley and Kent C. Dodds, which a lot of this article is based on:

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.