Borislav Hadzhiev
Reading time·5 min
Photo from Unsplash
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.:
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.
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.
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).
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 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.:
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.
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.
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.:
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:
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.
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.
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
.
Check out these talks from Tanner Linsley and Kent C. Dodds, which a lot of this article is based on: