Skip to content
ReactField

TopicsIntermediate

State Management in React Applications

Choose where React state should live: local UI state, Context, reducers, server caches, and external stores, with decision paths for production apps.

Mohamed ElshahawyPublished Apr 2026Updated Oct 2026
20 min read

What you'll learn

  • Starting paths for greenfield, SSR, Redux and server-heavy apps
  • Match decisions like prop drilling or fetch-vs-store to an approach
  • Categories of state and their local or global nature
  • Separating server state from client state, then comparing store models

State management is less about picking a trendy library and more about knowing which data is UI chrome, which is a reflection of the server, and which truly needs to be shared.

This page is the state cluster hub. Use it to pick an approach, then open a focused guide for placement, Context vs stores, or anti-patterns.

Use the path picker when you know your situation. Use the decision matcher when you are stuck between two options. The rest of this guide keeps the deep definitions, comparisons and overlaps.

Four short paths from a state toolbox toward local state, fetch cache, Redux and SSR
Start from your situation, then add tools only when sharing or caching hurts.

Pick a client state path

Try it: client state path

Pick your situation. You get a short ordered approach, why it fits, and a first-week plan.

Your path

Local first, then fetch, then a small store

Unknown scope is normal at the start. Maximize local state, add a server cache for API data, and introduce a store only when sharing hurts.

  1. useState

    Default for UI and form state.

  2. TanStack Query

    Server state cache for fetches and mutations.

  3. Zustand

    Small global client store if prop drilling appears.

  4. Proficiency with Hooks

    Effect and state patterns that keep local state clean.

First week

Build one screen with useState only. Add TanStack Query for one list endpoint. Do not add Redux yet.

The quick tabs below still show greenfield, Redux and combination notes. The path above is the decision layer.

Checkpoint

You are starting a greenfield SPA with unknown scope. What should you do first?

Match a state decision

Decision labels connected to approach cards for local state, fetch cache and stores
Name the decision, then pick one primary approach.

Try it: match a state decision

Choose the decision you are stuck on. The matcher points to a primary approach and an optional alternate.

The decision

The same client value passes through many components that do not care about it.

Start here

Zustand

A small store or a focused context beats threading props through unrelated layers.

Also consider

useContext

Context is enough when updates are rare and the tree is small.

Related: State management anti-patterns

Overlaps between context, stores and fetch caches are normal. The common overlaps section below goes deeper.

Checkpoint

API list data that can be refetched usually belongs where first?

Key takeaway

Server data usually belongs in a fetch cache. Ephemeral UI belongs in local state. Global stores earn their place when many distant components share the same client data.

TLDR: Starting Points for Client State Management

2. Need More Functionality?


Does any of this apply to SSR (Next.js / Remix)

In general, this guide applies to managing state on the client (browser). Since the entire premise of SSR is SERVER-side, this guide doesn't really apply to managing any state on the server. To summarize:

  • Even apps that use SSR might also need to manage state on the client
  • Simple CRUD-like (opens in a new tab) applications often means you can shift/move some of your client state to the server

Term Definitions

Before explaining this approach to State Management, let's get on the same page regarding some phrases frequently used.

Categories of State In Web Apps

A browser window connected to four tiles labelled Server, Client, URL and Form
Most app state falls into one of these buckets, and each bucket has tools that fit it best.

Outlined here are the various categories data your application might need to manage. The distinction matters because some state-management tools fit certain kinds of state better than others.

If you're interested in a more detailed definition of each state category below, you might take the time to read The 5 Types of React Application State (opens in a new tab). Think of these categories as out of your control; this is simply the nature of the web and how the industry has decided to categorize information.

Local or Global: Developers Choice

Left, labelled Local: one component carrying its own orange backpack of state. Right, labelled Global: one shared orange store connected to four components
Local state stays with the component that owns it. Global state is shared by many.

Categorically speaking, ALL state we manage in applications can be written as either "local" or "global" state - a piece of state becomes global based on if its exposed and consumed by the rest of the application (in other components) without passing the data as props. None of the types of state we covered above (1-5) are inherently local or global. For some types, it may seem obvious - a trivial example of this is the UI state - of which you should use React hooks (like useState and useReducer) to manage, almost always. Technically you could configure some UI state to be in a global store such as Redux, but in practice, there's rarely a need for that type of state data to be accessible globally, and configuring it that way will eventually cause performance issues in your application.

To restate our point: you, the developer, must decide what state data should be local vs. global. The trait that defines if a piece of state is local or global is whether the state data is accessed in multiple components without being passed as props.

  • If other components (like children) receive state via props, that is local data
  • If other components (like siblings or distant relatives) receive the same state data via hooks/functions, that is global data that's been injected/selected into your component

The Focus of this Guide Is Not Routing or Sessions

Now that you know all the types of state, the remainder of this guide will focus heavily on UI State, Communication State, and Application State (1, 2, and 3 above).

Those specific 3 types are the focus because the other types (4. Session State and 5. Location state) can easily be solved for us by tapping into the vast React ecosystem without much consideration. Or, put differently, most apps require the same functionality when it comes to routing and token storage, so there's not much to solve.

It's infrequent that you should write a custom location state manager when plenty of better options already exist for managing navigation history (like react-router-dom (opens in a new tab) or reactnavigation (opens in a new tab) for React-Native apps).

And for session state - there is very little complexity to storing tokens (but some overlap with other session data you can read in the Overlaps section below), so here's a summary:

More On Authentication

Common Uses of The Tools We Know

There are three primary approaches to managing application state:

  • Internal React APIs (useState, useReducer, useContext)
  • "Smart" Data-fetching libraries (tanstack-query, rtk-query, swr, etc.)
  • Global stores (Redux, Zustand, Jotai, etc.)

Managing state is never as simple as "always use X library for Y requirement". Below are some quick examples of how each approach can manage state in both localish/globalish ways:

  • React APIs: can handle local state AND global state
    • Ex: useState for a local toggle in a component, useContext for global dependency injection into components throughout your React tree
  • Data-fetching libraries: a blend of local & global state
    • Ex: using an API endpoint response in only one component (localish, because that data is only accessed in one container) or re-using the cached response data from that same API endpoint in another component (globalish now, because the data is accessed via a selector, the url, instead of as a prop)
  • Global stores: for handling exclusively global state
    • Ex: The most common use for global stores are when many components need to access the same information. Even if a global store is used to manage some UI state (which you shouldn't do), that state data would be configured so that its globally accessible in all other components.

Note there is some overlap above - each can solve local/global state to a degree, some just work better for each than others. Again, it is your choice to make your state data hidden in a component (local) or exposed and accessed in other parts of your React tree (global).

However, developers often do reach for global state manager libraries too soon. There is a balance that we need to get right, otherwise we'll end up with bloated applications. Before reaching for a global state, lets cover the ways we can maximize local state in our React app (both for performance and maintainability).

Maximize Local State Best-Practices

Now that you understand the differences between local and global state - you need to understand how far you can take local state before introducing a global store in your application.

When Do You Need Global State?

  1. If you're still prop drilling a lot (after trying the above approaches to lifting state) or feel you're headed in that direction and want to avoid it
  2. If you have some type of state/store that lives outside of your React application, but you need to consume it in your React app

If you've come this far and now you know you need a global state - the next question is - what are your options? Here are the two popular approaches you can take:

  1. Data Fetching & Server Cache (tanstack-query, rtk-query, swr)
  2. Global Stores (Redux, Recoil, etc.)

There are times where you might not need an entire state manager. If your global state is relatively small/simple, you might be fine with one of the following instead:

  • URL
  • Cookie
  • Local/Session Storage
  • IndexDB

Why Not Context?

The React docs (opens in a new tab) explain how to use React Context for more global-like state management. While this can be a suitable solution for simple applications, we find that using Context correctly is typically hard to get right as a global state manager, especially if you have lots of data and its frequently being accessed by various trees/nodes in your application.

Our upcoming advanced State Management Guide will explain how to use Context correctly, when it's a good choice, and why we think there are better options (opens in a new tab). The takeaway here is that an efficient (optimized for re-renders) solution for global state management is not an easy task with Context.

1) "Smart" Data Fetching

Before exploring global stores - you should be aware of a common situation in a lot of apps where people *think they need a global store - but don't actually. Picture this React app: a dashboard behind authentication with only several routes (pages). One of the routes simply fetches a dataset from an API and renders a bar graph based on that dataset. Then, a second route also fetches some data from an API, but has a <form /> on the same page that allows the user to update or mutate that data. After the user submits the form, the application should re-fetch the data from the API to have the new data instead of the old stale data the user previously saw.

If you can step back and observe this at a high level… often, this basic CRUD (opens in a new tab) behavior (fetching data, mutating data, then re-fetching data because we know it's stale now) is the majority of an application's state.

The way these data-fetching libraries work to simplify your state manager - is they cache the response of each API endpoint that's getting requests from your app and let you easily re-access the response. The basic idea is when you have multiple components consuming the same endpoint data - the API only gets hit once instead of 3 times (once when each component is rendered) and that response is turned into a global state that gets shared.

The library only needs you to pass the URL string and some options that dictate when the cache should be cleared + refetched. The cache provides you a global store to consume data from in your components, albeit, a pretty simple one that expects all of your data is coming from an API.

TLDR: If most of your app state simply reflects your server state and sharing the server API responses between components, "smart" Data Fetching is probably all you need. Here are some additional things that data-fetching libraries also tend to solve:

  • Polling on a time interval
  • Revalidation on window focus
  • Revalidation on network recovery
  • Local mutation (Optimistic UI)
  • Smart error retrying
  • Pagination and scroll position recovery

This client cache is only one of the layers that can hold old data. The server, the framework and a CDN can cache too; Update Content Without Redeploying walks through each one.

When Data-Fetching Isn't Enough

When you're unsure if smart data-fetching will be enough or if you'll need a "store" of some kind, ask yourself the following questions:

  • Will the data in your React app go for extended periods without the need for re-fetching from the API?
  • Will the state/data in your React app likely get out-of-sync with the server state, intentional or not, during regular application usage?
  • Will your application state be updating data frequently enough that its unreasonable for EVERY change to be persisted to the server immediately when they occur?
  • Will your server state update frequently enough that your React app might not know when the cache is stale and data should be re-fetched?
  • Is it mission-critical that your application shows the latest (uncached) reflection of server state at all times?

If you answered "Yes" to any of the questions above, you should consider a more centralized global state as a potential solution for more complex requirements.

2) Global Stores | Singleton vs. Atomic vs. Proxy

Three panels: one large shared store, many small atoms, and a proxy mirror feeding components
Singleton, atomic and proxy models share data differently. Pick the shape that matches how you update.

The different recommended approaches for global stores are:

  1. Singleton - the singleton (AKA "module") architectural approach typically means a single instance of the state (called a "store") that is shared across the entire application. Typically this means one large object that holds ALL state values in your application.
  2. Atomic - the atomic pattern (often associated with Recoil & Jotai) all centers around the idea of an "atom" - an isolated piece of state. Atoms are consumed & mutated from anywhere in your application. This pattern differs from the singleton approach above by splitting your state into smaller related groups of data that allow more granular interactions with global state data. Atoms can also be grouped together to derive state.
  3. Proxy - this architecture pattern introduces a proxy object for you to interact with, while that proxy communicates with the source of truth (the store). This approach is intended to make modifying the application state simpler by allowing any assignment/mutation and the proxy forwarding those updates back to the central store (this is the antithesis or opposite of "immutability", really)

Next, you can read more about the tradeoffs and benefits each architecture offers 👇

Table Comparison

The links below take you to bundlejs.com to demonstrate what the bundle size might result in (using latest versions).

PatternLibraryLinks to Bundle SizePopularity (weekly downloads)
Flux Moduleredux-toolkit (opens in a new tab)bundlejs.com (opens in a new tab)2 million
Flux Modulezustand (opens in a new tab)bundlejs.com (opens in a new tab)1.5 million
Flux Atomicrecoil (opens in a new tab)bundlejs.com (opens in a new tab)400k
Flux Atomicjotai (opens in a new tab)bundlejs.com (opens in a new tab)450k
Proxymobx (opens in a new tab)bundlejs.com (opens in a new tab)1 million
Proxyvaltio (opens in a new tab)bundlejs.com (opens in a new tab)230k
What is FLUX?

Common Overlaps Between Different State Managers

As we've just been through many state solutions - undoubtedly there's some "overlap" between what they're able to do. When there's overlap, it can be confusing as to which direction you should take. There are some recommendations below for questions you'll frequently run into while deciding the best ways to manage application state.

The Overlaps Between Each Approach to Global State

  • Contexts / Stores share a very similar approach (avoiding prop-drilling to share data), Stores just tend to have more features out of the box and better options for re-render optimizations
  • Stores / Data-Fetching Caches also share a similar approach (sharing the same data between components without props / prop-drilling), the data-fetching solutions just assume all/most client state is simply a reflection of the server state (retrievable via API endpoints)
  • All 3 solutions (Contexts, Stores, Data-Fetching) consume data from "somewhere else" and use it for things like rendering, mutations/actions, etc.

Specific common overlaps between "App" State and "Session" State

  • Keep things like tokens in localStorage, and the actual data those tokens receive in your contexts/stores. The reason is: the token rarely plays a role in the logic/views of your app. It's usually just included in API requests.
    • The performant way to store tokens would be to configure your requests globally to use the same API token retrieved from localStorage (just once) instead of constantly retrieving the token from localStorage and then including it in your requests as an HTTP header in each component (this is massive duplication of code and easily avoidable)
  • Secondly: should you put "user" info in a store or a context, like a UserContext/AuthContext or something?
    • If the auth is handled by the same system/API that you're getting your app data from - just throw it all in the store. Arbitrarily mixing contexts with global stores isn't necessary and would be a confusing division of data
    • If the auth is handled by a 3rd party system like Okta, Clerk, OAuth, etc. you could probably just wrap the user/profile info provided back by those parties in a context (since that data will rarely change) and then keep the rest of your business/app data in stores/caches

Thanks / References

Thanks for reading! If you have any suggestions on how developers can approach managing state in their React applications, let's talk (You will get credit for your contributions!).

Specific thankyou to editors of this guide:

State management checklist

  • I can name whether a piece of data is UI, communication, app, session or location state
  • New UI state starts local unless sharing is proven
  • Server data uses a fetch cache or framework data API when the server is the source of truth
  • Tokens are not mixed into hot UI stores without a reason
  • Form drafts are not keystroke-updating a global store
  • I know why I picked Context, a store or a fetch cache

Further reading

Conclusion

Libraries change. The categories of state do not. Decide local versus global, client versus server, then pick a tool that matches the job.

The key takeaway

Maximize local state, put server data in a cache, and add a global store only when sharing is real.

Check your understanding

Test yourself6 questionsQuestion 1 of 6

For a greenfield SPA with unknown scope, what is the best first move?

Decision framework

SituationPrefer
Isolated UI chromeLocal useState
Low-frequency shared configContext
Complex local transitionsuseReducer
Remote data with cache/retryServer-state library
Many distant readers, frequent updatesExternal store

Also see Where should state live? and Context vs Zustand.

When not to add a library

  • Prop drilling through two levels is still fine
  • You have not measured a re-render or sharing problem

Checklist

  • Separated server state from client state
  • Local first, then lift, then Context/store
  • No derived state synced only via useEffect

Recap

What to remember from this article

Sections read

0

Questions

0

none answered yet

Quiz

0

not taken yet

Up next: Where Should State Live? · 2 min

Continue