Pavan Rangani

HomeBlogYou Might Not Need a State Management Library

You Might Not Need a State Management Library

By Pavan Rangani · August 18, 2026 · Web Development

You Might Not Need a State Management Library

Reaching for a state management library is one of the first things a React team does, often on day one, before there is any state worth managing. React state management has a reputation as a hard problem that demands heavy tooling, and for a certain class of application it does. But a great deal of the perceived difficulty dissolves once you draw one distinction, and many apps that installed Redux never needed it. This is not an argument against libraries; it is an argument for reaching for one when the problem actually appears.

The distinction that changes everything: server state is not UI state

The single most clarifying idea in front-end state is that there are two completely different kinds of state, and lumping them together is the root of most of the mess.

Server state is data that lives on the server and you cache a copy of on the client — the list of products, the current user, the search results. It is not really yours. It can be changed by other users, it goes stale, it needs refetching, it has loading and error conditions. UI state is state that only exists on the client and belongs entirely to the interface — is this dropdown open, which tab is active, what has the user typed but not yet submitted.

These need utterly different tools, and the classic mistake is treating server state as if it were UI state — dumping fetched data into a global store and then hand-writing all the caching, refetching, and invalidation logic that a purpose-built tool would give you for free. Most of what teams struggle to manage in Redux is server state wearing a costume.

Most of your “global state” is a server cache

If you audit what an average app keeps in its global store, the majority is server state: the current user, some reference data, the contents of the current page. That is not global application state in any meaningful sense — it is a cache of the server, and it should be managed by a tool built for exactly that.

A data-fetching library — TanStack Query is the common choice — handles server state as its whole job. It caches responses, dedupes concurrent requests for the same data, refetches when data goes stale, retries failures, and exposes loading and error states without you writing any of it. Move server state into one of these and your “global state problem” often shrinks to almost nothing, because most of it was never global UI state to begin with. The caching questions it answers for you are the same ones from our caching strategies guide, just solved on the client.

Developer working on a React application interface
Most of what lands in a global store is server state — a cache of the backend, not UI state.

What is left is small, and React handles it natively

Once server state is out of the picture, the genuinely client-only state that remains is usually modest, and React’s built-in tools cover most of it without any library at all.

useState handles local component state, and the first rule is to keep state as local as possible — a dropdown’s open flag belongs in the dropdown, not in a global store, and lifting it higher than it needs to go is how simple things become tangled. When several components need the same piece of UI state, useContext shares it down a tree — the current theme, the authenticated user object, the active locale — without prop-drilling. For a self-contained widget with several interrelated values and transitions, useReducer keeps the logic in one predictable place. Between them, these three cover the great majority of real UI state.

The caution with Context is that it re-renders every consumer when its value changes, so it suits state that changes rarely — theme, user, locale — and not high-frequency state like form input on every keystroke. Used within that grain it is exactly right and needs nothing more.

When you genuinely do want a library

This is not a claim that state libraries are pointless — they solve real problems, and knowing when you have one is the point. Complex client state shared across a large tree and changing frequently, where Context would re-render too much, is a real case for a focused store like Zustand or Jotai. Elaborate state that many disconnected parts of a large application read and write, with a need for strict predictability and time-travel debugging, is where a fuller solution earns its weight.

The point is sequence, not prohibition. Reach for the library when you feel the specific pain it solves — too many re-renders from Context, genuinely global client state that is awkward to thread through the tree — not preemptively on day one because every tutorial installed one. A library adopted to solve a problem you have is an asset; one adopted to prevent a problem you might someday have is complexity you carry for free.

The URL is a store you already have

One category of state deserves special mention because teams routinely put it in the wrong place: state that describes what the user is looking at — the current filters, the search query, the active tab, the page number, a selected item. The instinct is to keep this in component state or a global store. The better home is very often the URL itself.

Consider a filtered, paginated list. If the filters and page live in component state, the URL never changes, and several things quietly break. The user cannot bookmark or share the view they are looking at, because the link goes to the unfiltered default. The browser’s back button does not undo a filter change, because nothing was added to history. And a refresh throws the whole view away, dumping the user back to the start. All of that is fixed for free by keeping the state in the URL as query parameters, because the URL is shareable, bookmarkable, survives a refresh, and integrates with browser history automatically.

/products?category=shoes&sort=price&page=3
# the view IS the URL — shareable, refreshable, back-button-aware

This is not a trick; it is using the platform’s oldest state mechanism for exactly what it is good at. The URL is a globally shared, persistent, user-visible store that every browser feature already understands, and putting view state there means you write less state-management code, not more — the router becomes your store for this category. It composes cleanly with the server-state tools too, since a data-fetching library can key its cache on the URL parameters, so navigating to a URL you have seen before is instant. Before reaching for any client store for “what is the user viewing,” ask whether it belongs in the URL, because a surprising amount of what teams manage in application state is really navigation state that the address bar was built to hold.

The order that keeps it simple

Separate server state from UI state first, because that single cut removes most of the difficulty. Put server state in a data-fetching library that was built for caching and refetching. Keep the remaining UI state as local as it will go, lift it only when sharing genuinely requires it, and use Context for the low-frequency cross-tree values. Then, and only then, if a real pain appears that these do not address, add a focused state library to solve that specific pain. Most applications never reach the last step, and the ones that do arrive there knowing exactly why — which is a far better position than having guessed on day one.

← Back to all articles