For years, "state management in React" meant picking one library and routing everything through it — Redux for a while, then a wave of lighter alternatives. What actually happened by 2026 is that the question split into three separate, smaller questions, each with a different right answer: where does UI-local state live, where does cross-cutting app state live, and where does server-derived state live. Treating all three the same is what made early state management advice feel heavier than it needed to be.

Local State Is Still Just useState

The most common state management mistake is still reaching for a global store to hold state only one component tree actually needs — a form's draft value, whether a dropdown is open, the active tab. If nothing outside that subtree reads it, it doesn't belong in global state regardless of which library you're using.

jsx
function AccordionItem({ title, children }) {
  const [open, setOpen] = useState(false);
  return (
    <div>
      <button onClick={() => setOpen(o => !o)}>{title}</button>
      {open && <div className="panel">{children}</div>}
    </div>
  );
}

Cross-Cutting State: Context Plus a Store, Not Context Alone

Context's known weakness hasn't changed: every consumer re-renders on any value change, because Context doesn't do selective subscription on its own. For state that's read broadly but changes rarely — theme, current user, feature flags — plain Context is fine. For state that changes frequently and is read by many components with different slices of interest — a shopping cart, a multi-step wizard's state — a store library with selector-based subscriptions avoids the over-rendering:

jsx
import { create } from 'zustand';

const useCartStore = create((set) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
  removeItem: (id) => set((state) => ({ items: state.items.filter(i => i.id !== id) })),
}));

function CartBadge() {
  // only re-renders when items.length changes, not on every cart mutation
  const count = useCartStore((state) => state.items.length);
  return <span className="badge">{count}</span>;
}

That selector — state => state.items.length — is the mechanism Context can't give you without extra work: a component subscribes to a derived slice, not the whole store, so unrelated updates skip it entirely.

Signals Made It Into React's Orbit, Not Its Core

Signal-based fine-grained reactivity — popularized outside React by Solid and adopted by Angular and Vue — has arrived in the React ecosystem as libraries (Preact Signals being the most common bridge) rather than as a core React primitive, because React's rendering model is fundamentally re-render-and-diff, not fine-grained dependency tracking. Where teams do adopt signals in a React codebase, it's usually for state that updates very frequently and needs to bypass component re-renders entirely — animation values, real-time data feeds — read directly by a DOM binding rather than triggering a React render at all.

jsx
import { signal } from '@preact/signals-react';

const mouseX = signal(0);

function Cursor() {
  // .value read here subscribes this component specifically, not the tree above it
  return <div style={{ transform: `translateX(${mouseX.value}px)` }} />;
}

Server State Is Not the Same Problem

The biggest shift in state management thinking has been recognizing that data fetched from a server — cached, potentially stale, revalidated on an interval or on focus — is not the same category of problem as client-only UI state, and forcing it into a Redux-style store meant hand-building cache invalidation, deduplication, and refetch logic that a dedicated data-fetching library (React Query, SWR, or a framework's built-in equivalent) already solves. Mixing the two in one store is the pattern most likely to produce stale-data bugs, because generic state stores have no concept of "this value expires."

The Practical Layering

A typical 2026 React app ends up with useState/useReducer for local component state, Context for rarely-changing global values, a lightweight store for frequently-changing cross-cutting state, and a data-fetching library for anything that originated from a server. The question worth asking before adding a dependency isn't "which state library is best" — it's "which of these four categories is this state actually in."