DEV Community

Cover image for Provider Pattern in React: Ending Prop Drilling for Good
Md Enayetur Rahman
Md Enayetur Rahman

Posted on

Provider Pattern in React: Ending Prop Drilling for Good

A practical summary inspired by *“Learning Patterns” by Lydia Hallie & Addy Osmani***


1  Why we need a Provider

Passing props through every layer of a component tree—prop drilling—quickly becomes unmanageable. When even a small rename forces edits across dozens of files, refactoring grinds to a halt. Hallie & Osmani highlight this pain point and introduce the Provider Pattern as the antidote.

“With the Provider Pattern, we can make data available to multiple components… without relying on prop drilling.”

How it works

  1. Create a Context with createContext().
  2. Wrap part (or all) of your app in <Context.Provider> and pass a value prop.
  3. Inside any descendant, access the data with useContext(Context).

Because the value comes straight from the nearest provider, intermediate components stay blissfully unaware.


2  Walk‑through: Light ↔ Dark Theme Switcher

Below is a pared‑down theme example. The goal: toggle between light and dark mode without threading state through every component.

// ThemeContext.js
import { createContext, useContext, useState } from "react";

const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState("light");
  const toggleTheme = () =>
    setTheme((t) => (t === "light" ? "dark" : "light"));

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

export const useTheme = () => {
  const ctx = useContext(ThemeContext);
  if (!ctx)
    throw new Error("useTheme must be used within a ThemeProvider");
  return ctx;
};
Enter fullscreen mode Exit fullscreen mode
// Toggle.js
import { useTheme } from "./ThemeContext";

export default function Toggle() {
  const { theme, toggleTheme } = useTheme();
  return (
    <button onClick={toggleTheme}>
      Switch to {theme === "light" ? "dark" : "light"} mode
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode
// ListItem.js
import { useTheme } from "./ThemeContext";

export default function ListItem({ item }) {
  const { theme } = useTheme();
  const styles =
    theme === "light"
      ? { background: "#fff", color: "#111" }
      : { background: "#111", color: "#fff" };

  return <li style={styles}>{item.label}</li>;
}
Enter fullscreen mode Exit fullscreen mode
// App.js
import { ThemeProvider } from "./ThemeContext";
import Toggle from "./Toggle";
import ListItem from "./ListItem";

export default function App() {
  return (
    <ThemeProvider>
      <Toggle />
      <ul>
        <ListItem item={{ label: "React" }} />
        <ListItem item={{ label: "Patterns" }} />
      </ul>
    </ThemeProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

3  Custom hooks & Higher‑Order Providers

Wrap context lookup in a tiny custom hook (useTheme above). Throw if the hook runs outside its provider to avoid silent bugs. For even cleaner code, you can create a higher‑order component that injects a provider around any subtree, decoupling context logic from rendering components.


4  Benefits at a Glance

  • No more prop drilling—data travels straight to consumers.
  • Targeted updates—only components consuming the context re‑render.
  • Centralised concerns—global themes, auth states, feature flags, or locale live in one place.
  • Easier refactor—renaming a value means touching a single provider, not a cascade of props.

5  Gotchas & Best Practices

Pitfall How to avoid
Over‑rendering when any value changes Split contexts so unrelated consumers don’t re‑render.
Mutable objects in value prop Memoise or keep primitive values to prevent loops.
Deeply nested providers Collapse related contexts into a composite provider.

6  When to Reach for Provider Pattern

Reach for it whenever many components need the same data, especially when that data is updated over time:

  • Authentication & user roles
  • Internationalization (i18n) strings
  • Theme toggles or design tokens
  • Feature flagging / A/B testing
  • Analytics context, socket connections, etc.

If just a few siblings need the data, plain props are still simpler.


Conclusion

The Provider Pattern frees your components from prop‑drilling chains and centralises shared state in one elegant stroke. Use it judiciously, wrap it in custom hooks or HOCs for polish, and your React codebase stays scalable and friendly to future refactors.


Content derived from “Learning Patterns” (Patterns.dev, CC BY‑NC 4.0).

Top comments (0)