Build a luckier tech stack today
Build a luckier tech stack today
Common Mistakes in React State Management (and How to Fix Them)

Common Mistakes in React State Management (and How to Fix Them)

On October 04, 2026

React gives you enormous freedom in how you manage state, and that freedom is where most of the trouble starts. A component tree that felt obvious at five components can feel like a maze at fifty, and the same three problems keep surfacing: props threaded through layers that don't care about them, re-renders that fire for no visible reason, and context treated as a universal store.

None of these are hard to fix. They are hard to notice, because they arrive gradually and every individual step seemed reasonable at the time. Here are the mistakes worth looking for, and what to do instead.

Mistake one: prop drilling through components that don't need the data

Picture a Dashboard that fetches the current user. The sidebar needs an avatar image. So Dashboard passes user to Sidebar, Sidebar passes it to NavList, NavList passes it to Avatar, and Avatar finally reads the URL. Three components in the middle now hold a prop they never touch, and every change to the shape of user ripples through all of them.

A little drilling is fine. Two or three levels, where each step genuinely passes something downwards, is clearer than any abstraction you could invent in its place. Explicitness is a feature. The mistake is letting the chain grow until the middle components are nothing more than couriers.

What to do instead

  • Pass components, not data. Let Sidebar accept a single children or avatar prop, so Dashboard decides what appears inside and NavList never learns that user exists.
  • Colocate the state. If only Avatar needs the user, read or fetch it there rather than at the top of the tree.
  • Use a scoped context. A provider wrapped around the section that needs the value beats a global one, and it keeps the dependency visible in the markup.
  • Reach for a store only when the data crosses the whole app. Authentication and theme often qualify. A single dropdown's open state does not.

Mistake two: panicking about every re-render

Re-rendering is how React works. A component rendering again is not a defect; a component rendering forty times while someone types, or redrawing an expensive chart on every keystroke elsewhere in the page, is worth chasing.

Start by measuring rather than guessing. The React DevTools Profiler shows which components rendered and why, and the "highlight updates" option makes cascades obvious in a second. If you'd rather not install anything, a counter incremented during render and logged in an effect is enough to confirm a suspicion before you spend an afternoon optimising.

Fix the cause, not the symptom

  1. Move the state down. If a search field's value lives in a parent that also renders a 500-row table, every keystroke re-renders the table. Keep the field's state in the field.
  2. Split the component. Extract the heavy child, or wrap the state in a small component that receives the heavy part as children. Elements created by a parent don't re-render just because the wrapper's state changed.
  3. Watch object and function props. An inline object or arrow function gets a fresh identity on every render, which quietly defeats memoisation downstream.
  4. Memoise deliberately. Memo, useMemo and useCallback all cost something in complexity. Add them where you've measured a problem, not as a default style.

Mistake three: using context as a state manager

Context is a transport mechanism, not a store. It answers "this value is needed far away", not "this value changes constantly". Every consumer re-renders when the context value changes, even if the component only reads a field that stayed the same.

So a single provider holding user, theme, notifications, feature flags and a live connection means one toast appearing redraws the entire app. That's the mistake in its purest form: bundling unrelated concerns because they happen to be available.

Split contexts by update frequency

Theme changes rarely. A notification count changes often. Those belong in separate providers so that a change in one doesn't wake the consumers of the other.

Separate state from dispatch

A common pattern is one context for the value and another for the setter or dispatch function. Because the function's identity is stable, components that only trigger updates don't re-render when the value changes.

Move genuinely hot state out of context

For high-frequency updates shared across distant branches, an external store with a subscription model — the kind React's useSyncExternalStore is designed for — lets components subscribe to just the slice they read. That's a heavier tool, and it's the right one when context has stopped scaling.

Mistake four: hoisting state higher than it needs to go

Lifting state is the standard fix for sharing, but it's easy to overshoot. State pushed too high re-renders too much, and it forces props back down through components that never asked for them.

Two specific versions of this cause most of the pain. The first is treating server data as client state: fetched lists and records belong in a query cache that handles loading, staleness and refetching, not in a hand-rolled global object. The second is syncing props into state with an effect, which creates two sources of truth that drift apart. If a component needs to reset when an identifier changes, pass a key and let React remount it.

A rough sorting rule

  • Used by one component? Keep it local.
  • Shared by a few nearby components? Lift to their nearest common parent.
  • Needed across distant branches and rarely changed? Context.
  • Came from a server? A query cache.
  • Complex, shared and updated constantly? A dedicated store.

Mistake five: storing values you could calculate

Derived state is a reliable source of bugs. Storing a full name alongside a first and last name, or caching a filtered list in state and forgetting to refresh it when the filter changes, means two values must be kept in step forever. Sooner or later they won't be.

Compute during render instead. Joining two strings or filtering an array of a few hundred items is cheap, and React will redo it only when the inputs change. Reach for useMemo when the calculation is genuinely expensive — sorting thousands of rows, say — and not before. If you're ever tempted to write an effect whose only job is to update state from other state, that's the signal to delete both.

Start with the smallest change

Pick one screen that feels sluggish or tangled, open the Profiler, and follow the renders. Nine times out of ten the fix is moving a piece of state down a level, splitting one component into two, or pulling a rarely changing value out of a crowded context provider. Do that, verify it with the tool, and move on. State management gets easier when you treat it as a series of local decisions rather than one architectural choice you have to get right on the first attempt.

Photo: MART PRODUCTION / Pexels

How to Write a Technical CV for UK Software Jobs

A practical guide to building a UK-style technical CV: the right sections, bullets that prove impact, correct British spelling, and the...

Explainer: What Is Edge Computing and Why It Matters for UK Latency

Edge computing runs workloads closer to users and devices. Here's how it works, where UK edge capacity sits, and when it actually cuts latency.