6 things I wish I knew about state management when I started writing React apps Veeral Patel Follow Feb 9 · 7 min read

I have a lot more blog posts like this I plan to write. If you like this, follow me on Twitter to make sure you don’t miss any.

Think about the most complex frontends you’ve used. Frontends that made you wonder — “how did they create this”?

Here are some of mine.

What makes these frontends complex?

State management. The frontend “knows” a lot of things, and these things interact with each other in non-trivial ways.

So in my view, state management is the core problem when developing a UI.

Here are some things I wish I knew about state management earlier.

1) State management is how you mitigate prop drilling

State management matters

To build a non-trivial React application, you need to consider state management, in my view.

You don’t need to use a third party library, you can use Context, but you do need to figure out how to store global state that can be accessed anywhere in your application.

Example: dark mode support

For example, say your app has a dark mode. All your rendered components must know what theme is on, so they can render the UI in the right color.

Other examples of global UI state include whether the user is logged in, the the logged in user’s username, and the value of the global search box.

Prop drilling isn’t the answer

Context didn’t exist when I started learning React, so to solve this problem, I prop drilled. As your app gets larger, prop drilling becomes impractical.

State management is the answer

What you need to do is store your theme setting in a Redux or MobX store, or in a plain JavaScript object, and pass it to all your components using Context.

Why don’t more people know this?

I had no idea state management was the solution to the prop drilling problem until I learned Redux, out of curiosity. “State management”, the term, doesn’t seem related to our problem. What is state? Why does it matter?

To make things worse, many articles say you can build non-trivial web apps without state management, which initially deterred me from even learning about it.

2) State management is how you update your page after creating/updating data, without a refresh

The problem

Say you’re writing a todo app. After the user creates or renames a todo item, you’d like to update only the todo list component — but not refresh the page. How do you do this?

The solution

The solution is to store the list of todo items in a global store in the client, along with a method for re-fetching the list of todo items from the server and updating our store.

Then, your todo list component reads the list of todo items from your global store. And after the user creates or updates a todo item, call the method for re-fetching the list of todos, which updates your global store, which updates your component.

I think of the list of to do items on the client as a client-side cache, a subset of the data on the server.

3) Manage your state maintainably by storing it in the right places

Don’t just put all your application state into whatever state management library you choose.

Instead, recognize that your application has several different kinds of state. I’m inspired by James Nelson’s post on this topic.

Data + loading state : the list of todo items your frontend renders and whether the list is loading. Put into Redux/MobX/etc.

: the list of todo items your frontend renders and whether the list is loading. Put into Redux/MobX/etc. Global UI state : whether the user is logged in, value of a global search bar. the server doesn’t store this data at all. Put into Redux/MobX/etc.

: whether the user is logged in, value of a global search bar. the server doesn’t store this data at all. Put into Redux/MobX/etc. Local UI state : whether an dropdown is expanded, for example. The rest of your frontend doesn’t care about this. Use component state

: whether an dropdown is expanded, for example. The rest of your frontend doesn’t care about this. Use component state Form state : the values of fields in a form. This is a subset of local UI state. Use a library like Formik to treat the form as a controlled component

: the values of fields in a form. This is a subset of local UI state. Use a library like Formik to treat the form as a controlled component URL state : the route the user is on now. Read and update window.location ; don’t create a second source of truth

: the route the user is on now. Read and update ; don’t create a second source of truth Page state: you have a page whose components interact with each other in a complex way, but not with components on other pages. Create a Redux/MobX store just for the page (or pass down a plain JS object with Context)

4) Learn other state management libraries in addition to just Redux

My struggles while using Redux

Redux is probably good enough for your application. But I didn’t like using it.

Why do I need to worry about normalizing the data from my server on my client? Why do libraries like Redux ORM need to exist? I don’t want to re-implement a bunch of my server-side code on the client.

Why do I need to touch multiple files and write a lot of boilerplate code in order to add a simple feature?

What is an action/action type/action creator/reducer/store again?

I understand the benefits of writing immutable, functional code, but writing Redux reducers feels needlessly unintuitive.

I’d like to simply make an API call in an action and update my Redux store without learning and using Thunk or Saga.

Switching to MobX

Then I learned MobX.

Each store is just a plain JavaScript class. There’s no boilerplate.

Each piece of state in a store is just a class variable.

Actions are just methods in the class with an @action decorator.

decorator. You can make API calls in your actions just like you normally would; just wrap the code that updates your state after the call with runInAction .

. It supports observability, which I don’t use much myself, but is a useful tool.

Find the right library for you

MobX may not be the solution for you. It may be apollo-link-state , unstated , xstate , or a home-brewn solution using Context.

But keep learning different libraries until you find one that fits you.

5) Your React app has two layers

State and view layer

I think of a React app as having two layers:

A JavaScript object, also known as your state, and methods that update it

And our React code, the view layer, which turns that JavaScript object into DOM elements

This is what people mean when they say “UI is a function of state” and “React is just a view layer”.

Benefits of this mental model

By embracing this paradigm, you can write testable, functional code, decoupled from your UI, for managing your state.

And your UI becomes easy to write. You have a JavaScript object with all the data your UI needs, structured in the way your UI wants it, with methods for updating this JSON object. Write your components easily with this “API”.

Keep in mind…

Not all state management libraries store state internally as a JavaScript object, but this is the way I like to think about it.

Of course, in practice, not all your state should be in your global state, as I write above. But this is a useful mental model anyways.

6) Shared vs non-shared components

Fetching data in all my components

When I first started writing React, I put my data fetching code in my components.

But this meant I couldn’t use a component in another place for its UI only.

Fetching data in my container components

Then, I read Dan Abramov’s article on presentational vs container components.

At first, I put my data fetching code in my container components, each of which rendered a presentational component.

But I realized I needed to put my data fetching code in MobX in order to update the UI after creates and updates (see #2).

Fetching data in MobX actions

Next, I put my data fetching code into MobX actions. Each container component connected to my MobX store and rendered a presentational component, passing data and methods from my MobX store as props as needed.

Unfortunately, this forced me to write a lot of boilerplate container components that effectively did nothing.

What I do now

I concluded that the right distinction is shared vs non-shared components.

Shared: Components you’ll need to render in more in than one place in your app. Can be either presentational or container. Examples: UserSelect, Button

Non-shared: Components you’ll only render in one place. Examples: UserTable, GlobalSearchInput

The presentational vs container component distinction assumes that you want to re-use presentational components, but not container components. I disagree.

You usually want to re-use some presentational components and not others, and you want to re-use some container components and not others.

Get in touch

Have a comment? Question? Leave a comment below!

Also, there’s a lot more great content coming. Follow me on Twitter to stay updated.