React Context

Context lets a React component make a value available to its entire subtree without passing it through every intermediate component as props. It's the built-in answer to prop drilling: threading theme, locale, or currentUser through five layers of components that don't use it themselves.

Context is a dependency injection mechanism, not a state management library. It's excellent for low-frequency, widely needed values and for scoping state to a feature. It becomes a performance problem when you put frequently changing, broadly consumed state into one big context, because every consumer re-renders on every change.

TL;DR

Quick Example

An auth context with a typed hook and memoized value:

In React 18 and earlier, render <AuthContext.Provider value={value}> instead of <AuthContext value={value}>.

Core Concepts

Creating, Providing, Consuming

Re-render Behavior

When a provider's value changes, React re-renders every component that reads that context, even if it uses only one field that didn't change. Components between the provider and consumers that don't read the context aren't forced to re-render by context itself.

Two consequences:

  1. A provider value created inline (value={{ user, setUser }}) is a new object on every render of the provider's parent, so all consumers re-render every time. Memoize it.
  2. Combining unrelated data in one context couples their update frequencies.

Splitting Contexts

A common refinement is to split state and dispatch into separate contexts, so components that only trigger updates (buttons calling dispatch) don't re-render when state changes. dispatch from useReducer is stable.

Context + useReducer

For feature-scoped state such as a multi-step wizard, a document editor, or a checkout flow, context with useReducer gives a small, dependency-free store:

Context vs Other Options

See state management and Redux for store-based approaches.

Best Practices

Try Composition Before Context

Often prop drilling can be avoided by passing components as children or as props, so the intermediate layers never need the data. Reserve context for values that many components at different depths need.

Wrap Context in a Custom Hook

Export useAuth() rather than the raw context. You get one place to validate that a provider exists, a clearer API, and freedom to change the implementation later.

Memoize Values and Keep Them Focused

Memoize provider values with useMemo, keep each context small and cohesive, and separate fast-changing from slow-changing data. The React Compiler can memoize values automatically, but splitting by update frequency still matters.

Place Providers as Low as Possible

A provider only needed by the checkout pages should wrap the checkout routes, not the whole app. Scoping keeps re-renders local and makes the dependency obvious.

Common Mistakes

Inline Provider Values

A Single Global "AppContext"

Putting everything into one context makes every consumer re-render on any change and turns the context into an untyped grab bag. Split by domain.

Using Context for High-Frequency Updates

Mouse positions, animation frames, or text input values in a context consumed across the app cause app-wide re-renders on every tick. Keep such state local, or use a store with selectors.

FAQ

Is Context a replacement for Redux?

For many apps, yes. Context plus useReducer handles modest shared state, and server state belongs in a data-fetching library anyway. Redux (or Zustand) still wins when you have lots of frequently changing global state, need selector-based subscriptions to avoid re-renders, or want devtools, middleware, and established patterns for large teams.

Why do all my consumers re-render when one value changes?

Context has no built-in selectors: any change to the provider's value re-renders all consumers. Split the context, separate state from dispatch, memoize the value, or switch to a store that supports selectors.

What's the default value used for?

It's returned when a component reads the context without a matching provider above it. That's useful for tests and optional contexts. Many teams set the default to null and throw in a custom hook, to make a missing provider an explicit error.

Can I use Context in Server Components?

No. Context is a client-side feature. In the Next.js App Router, providers must be Client Components ("use client"), though they can wrap Server Component children. See React Server Components.

Related Topics

References