State Management

Concept

Deciding where the values a screen has to remember live, and who is allowed to change them.

Why it exists

When a screen was one page, there wasn't much to remember. The text in an input, which tab is open — hold those inside that page and you're done. Where a value is needed and where it lives are the same place, so there is nothing to decide and no vocabulary needed for it.

Splitting screens into reusable components created the problem. The logged-in person's name is needed by the profile area at the top, the author column in the document list, and the owner field in the sharing dialog. Keep the value in one place and it has to be carried to each of those; make the path four levels deep and the parts in between receive a value they never use and pass it down. Keep a copy wherever it's needed instead, and changing the name in one place leaves the others showing the old one until somebody reloads the page.

State management is setting rules for where those values sit and how they move. It looks like a question of which library to use, and it is actually a design question about the location of values and the right to change them. The library only enforces whatever you already decided.

How it works

Work out the scope each value has to live in, then put it somewhere that matches that scope and nowhere wider. Four judgments in practice.

  1. Separate values by lifetime and scope. Text being typed can be thrown away when the screen closes; login details have to stay visible to the whole application for as long as the session lasts. Lift the first kind into global storage and unrelated parts of the interface redraw whenever one screen changes — a list that redraws on every keystroke in a search box is what this mistake looks like from the outside.
  2. Handle values fetched from a server separately. A document list is not something the screen remembers; it's a copy borrowed from wherever the original lives. When to fetch it again, what to show while fetching, what to do on failure all travel with it, which is why this category now gets its own dedicated tooling rather than sharing the general store.
  3. Narrow where changes can happen. Let any part modify a value directly and there's no way to trace when or why it changed. Route every change through a handful of named functions and the path of changes becomes a record — whether that record exists is the difference between half a day and two days when a bad value has to be tracked down.
  4. Lift less into global storage. Global is convenient to write into and hard to remove from. Once nobody can tell which screens read a value, nobody can delete it either, and six months later there's a block of state whose live parts nobody can identify.

The simplest way to move a value is a parent handing it to a child. When that gets deep enough to hurt, the condition has a name — props drilling — and most global stores and context tools exist mainly to reduce it.

Where it shows up

Login details and permissions come first. Who is signed in and whether they can edit this document are values nearly every screen has to agree on. Held separately, a permission change leaves screens still acting on the old one: users experience it as a bug that won't stay fixed, and since a refresh clears it, reproducing it is difficult.

Second is work that spans screens. In an SPA, where the browser handles navigation itself, this is where trouble shows up most often. A checkout flow collecting values across three screens has to keep what was entered when someone goes back and returns. That value can't live in any one screen because screens get discarded, and it doesn't need to be visible to the whole application either, so the answer is usually a place that exists for the duration of the flow and is cleared at the end.

Third is fetched lists and how they appear. This is where UI state, the way one element looks different depending on circumstances, interlocks: loading, empty and failed each need a decision about what the screen shows. Leave those three undecided and a failure renders as an empty list, and the user reads that as their documents having been deleted.

There's also a place to leave alone. A value used inside one part belongs in that part. Lift something like whether a panel is open into global storage and you spend the time later working out where on earth it gets changed.

How it sounds at work

"Don't lift this into global. Keep it inside this component."

Narrowing the scope in order to narrow what redraws.

"Server-fetched lists don't go in the global store."

Values whose original lives somewhere else belong in a different category entirely.

"I can't find where this value gets changed."

What happens when the places allowed to change a value were never narrowed down.

The problem surfaced on Pagedeck's document editor. After a sharing permission changed, the editor's buttons stayed on the old permission, because the top bar and the editor were each holding their own copy of it.

Theo lifted only permissions and login details into one place, treated the document body and the list as server-fetched values with their own rules, and left the text being typed inside its own component. Splitting the three categories came before any discussion of tooling. No new library went in, and the tickets about stale permissions stopped arriving after that release.

What people get wrong

The most common mistake is reading it as a choice of library. There are several well-known candidates, so which one you pick feels like the decision. But changing tools without deciding where values belong leaves the same problem in place. Install a store without separating what goes global from what stays local, and values that used to be scattered across many places end up scattered inside one place instead.

Believing global storage means values are managed is the other one. Everything is visible from one file, which feels tidy. But a global store is easy to add to and hard to remove from. Once it's hard to trace which screens read a value, nobody makes the decision to delete it, and as the store grows it becomes harder to predict what else changes when one value does. Teams that write down the criteria for going global before they need them suffer less later.

Easily confused with

TermWhat it isHow it differs from state management
Props drillingPassing a value down through many levelsA symptom that appears when value-passing gets deep. State management is the whole job of deciding where values live and who may change them, symptom included

Where to go next

  • Frontend

    see which field this problem belongs to

  • Component

    read about the unit that passes values around

  • UI state

    see how these values surface on screen

Last updated 2026-09-16