Home/Learn/React/Context — Sharing Without Prop Drilling

Context — Sharing Without Prop Drilling

Intermediate
Patterns

Context passes a value to a whole subtree without threading props through it. It is a delivery mechanism, not a state manager, and every consumer re-renders when the value changes.

Overview

Context solves one problem: a value needed deep in the tree, passed through components that do not care about it. Theme, session, locale and a feature-flag set are the canonical examples. The trap is treating it as global state, because context has no selector — when the provider's value changes, every consumer re-renders regardless of which part of the value they read. That is fine for a theme that changes twice a session and expensive for state that updates on every keystroke.

The Standard Setup

Provider, a guarded hook, and a value that is not recreated on every render.

useMemo the value; guard the hook
const ThemeContext = createContext(null)

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('system')

  // Without useMemo this object is new on EVERY render of the
  // provider, so every consumer re-renders even if theme is the same
  const value = useMemo(
    () => ({ theme, setTheme, isDark: theme === 'dark' }),
    [theme],
  )

  return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
}

export function useTheme() {
  const ctx = useContext(ThemeContext)
  if (!ctx) throw new Error('useTheme must be used inside <ThemeProvider>')
  return ctx      // never undefined from here on
}

// Export the hook, not the context. Consumers then cannot use it
// outside a provider, and you can change the implementation freely.

The Re-render Problem

No selectors, so splitting the context is the tool you have.

Split by concern and by update frequency
// One context holding everything
const AppContext = createContext({ user, theme, cart, notifications })
// A component reading only 'theme' re-renders when the cart changes.

// Split by update frequency and by concern
<AuthProvider>          {/* changes on sign in/out */}
  <ThemeProvider>       {/* changes rarely */}
    <CartProvider>      {/* changes often */}

// Split state from setters, so dispatch-only consumers stay still
const StateCtx = createContext(null)
const ActionsCtx = createContext(null)      // this value never changes

// What context is good at:
//   theme, locale, current user, feature flags, a form's registry,
//   anything a subtree needs and that changes rarely

// What it is bad at:
//   high-frequency state, large objects many components slice
//   differently — that is what Zustand or Redux selectors are for.

Do You Need It At All

Composition removes a surprising amount of prop drilling without any context.

Try composition before a provider
// The problem people reach for context to solve
<Page user={user}>
  <Layout user={user}>
    <Sidebar user={user}>
      <Profile user={user} />     // only this one actually needs it

// Composition — pass the rendered element instead of the data
<Page>
  <Layout sidebar={<Sidebar><Profile user={user} /></Sidebar>} />
</Page>
// Layout and Sidebar never mention 'user' at all.

// Two levels of prop drilling is not a problem worth a provider.
// Reach for context at three or more, or when many scattered
// components need the same value.

// Multiple providers stack into a pyramid quickly. Compose them:
function Providers({ children }) {
  return (
    <QueryClientProvider client={queryClient}>
      <AuthProvider><ThemeProvider>{children}</ThemeProvider></AuthProvider>
    </QueryClientProvider>
  )
}

Key Points to Remember

  • 1Context delivers a value to a subtree; it is not a state manager and has no selectors
  • 2Every consumer re-renders when the provider value changes, so memoise the value object
  • 3Split contexts by concern and by update frequency, and separate state from actions
  • 4Passing rendered children removes much prop drilling without any context at all
  • 5Use context for rarely-changing shared values; use a store for high-frequency state many components slice differently

Interview Questions

Sign in to ask Aria
1

Why should a context provider memoise its value object?

Medium
2

What is the main performance limitation of context?

Hard
3

How can composition reduce prop drilling without context?

Medium

Ask Aria about Context — Sharing Without Prop Drilling

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…