obah sylva

React Context API: When to Use It Instead of Prop Drilling

Share:fXinW
Share this article

Starting in React 19 (stable since December 2024), a context object can be rendered directly as a provider — <ThemeContext value="dark"> instead of <ThemeContext.Provider value="dark">. It’s a small syntax change, but it’s a useful marker for a bigger question every growing React codebase eventually hits: when does passing props down the tree stop being simple, and when is Context actually the better tool?

What Prop Drilling Actually Looks Like

Prop drilling isn’t a mistake — it’s just what happens when a value needed by a deeply nested component gets passed through every layer in between, whether or not those intermediate components use it themselves.

function App() {
  const [user, setUser] = useState({ name: 'Jane', role: 'admin' });
  return <Layout user={user} />;
}

function Layout({ user }) {
  return <Sidebar user={user} />; // doesn't use user, just forwards it
}

function Sidebar({ user }) {
  return <UserPanel user={user} />; // doesn't use user either
}

function UserPanel({ user }) {
  return <Avatar name={user.name} />; // finally uses it
}

Three of those four components only exist to forward a prop they never read. It’s harmless at this depth, but every added layer, and every additional value that needs threading through, makes refactoring riskier — renaming or restructuring the tree means updating prop signatures on components that have nothing to do with the data itself.

Solving It With Context

Context lets any component read a value directly from the nearest matching provider above it, no matter how many layers separate them:

const UserContext = createContext(null);

function App() {
  const [user, setUser] = useState({ name: 'Jane', role: 'admin' });
  return (
    <UserContext value={user}>
      <Layout />
    </UserContext>
  );
}

function Layout() {
  return <Sidebar />; // no user prop to forward
}

function Sidebar() {
  return <UserPanel />; // no user prop to forward
}

function UserPanel() {
  const user = useContext(UserContext); // reads directly
  return <Avatar name={user.name} />;
}

Layout and Sidebar no longer mention user at all — they’re free to be restructured, reordered, or reused without touching data they never cared about.
Diagram comparing prop drilling through multiple components versus a React context provider reaching a deeply nested component directly

The React 19 Provider Shorthand

The example above already uses it: rendering the context object itself — <UserContext value={user}> — instead of <UserContext.Provider value={user}>. The older .Provider syntax still works and isn’t broken by upgrading, but React’s own documentation now recommends the shorthand for new code, and a codemod exists for migrating existing providers.

Reading Context With use() Instead of useContext

React 19 also introduced use(), a more flexible way to read context that — unlike useContext — can be called conditionally or inside loops:

function Avatar({ show }) {
  if (!show) return null;
  const user = use(UserContext); // valid: use() can follow a conditional return
  return <img src={user.avatarUrl} />;
}

useContext is still fully supported and remains the more familiar choice for straightforward, unconditional reads; use() is the newer, more flexible option, most valuable when a component’s need for context genuinely depends on a condition.

When Context Is the Wrong Tool

Context solves prop drilling; it doesn’t replace a real state management solution. It falls short when:

  • State updates are frequent and high-frequency (every context consumer re-renders on every value change, since Context doesn’t do selective/partial subscriptions the way a library like Zustand or Redux does)
  • The app needs devtools features like time-travel debugging or middleware
  • State is deeply relational and consumers need to subscribe to just a slice of it, not the whole context value

For global-but-infrequently-changing values — theme, authenticated user, locale, feature flags — Context is usually the simplest correct answer. For state that updates on every keystroke or every animation frame and is read by many components, a dedicated state library will scale better.

Frequently Asked Questions

Is Context a replacement for Redux or Zustand?
Not generally — Context is a plumbing mechanism for passing a value down the tree, not a state management library. It has no built-in concept of selectors, so any change to a context value re-renders every component consuming that context, even ones that only care about part of it.
Do I need to migrate all my .Provider syntax to the new React 19 shorthand?
No — .Provider still works and there’s no functional difference today. It’s worth adopting in new code since React has signaled it will eventually deprecate .Provider, but existing code isn’t broken by staying as-is.
Can I use multiple contexts in one component tree?
Yes, and it’s common — a typical app might have separate contexts for theme, authenticated user, and locale, each with its own provider, nested as needed.

Want more React deep dives like this? Explore more Web Development articles.

Scroll to Top