Skip to content
RungsySign in

Context & State Sharing

Avoiding prop drilling without accidentally re-rendering your whole tree.

35 minDifficulty 3/5react · stateAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.

Before this

Why this exists

A theme value needs to reach a deeply nested button, six components down, and none of the components in between actually care about it — they just have to accept it as a prop and pass it along, cluttering every layer's interface with data they don't use. Context lets a value skip straight from a provider to whatever descendant actually needs it, without every intermediate component knowing it exists.

The mental model

Think of context as a value broadcast down a specific branch of the component tree. A `Provider` component sets the value for everything nested inside it; any descendant, no matter how deep, can read it with `useContext` — as if it were a global, but scoped to that branch of the tree rather than the whole app.

How it works

Context solves prop drilling, not general state management

Prop drilling is passing a value through components that don't use it, purely to reach one that does. Context removes that pass-through burden. It doesn't, on its own, solve where the state lives or how it changes — you still typically pair it with `useState` or `useReducer` somewhere above the provider to actually own and update the value.

Every consumer re-renders when the context value changes

When a Provider's value prop changes, EVERY component that calls `useContext` for that context re-renders — regardless of whether it reads the specific part of the value that changed. This is fine for infrequently-changing values like a theme, but a context updating on every keystroke can re-render a much larger part of the tree than expected.

A new object literal as the value prop breaks memoization

`<Context.Provider value={{ theme, setTheme }}>` creates a brand-new object on every render of the provider component, even if `theme` itself hasn't changed — every consumer sees a 'different' value by reference and re-renders unnecessarily. Wrapping the value in `useMemo` keyed on the actual dependencies avoids this.

Splitting contexts limits the blast radius of a change

Bundling frequently-changing data (like cursor position) and rarely-changing data (like a theme) into one context means every re-render of the frequent value also re-renders every consumer of the theme. Splitting them into two separate contexts lets each set of consumers re-render only when the specific data they actually use changes.

The mechanism

A Provider component wraps a subtree and supplies a value. Any descendant anywhere in that subtree — regardless of nesting depth, and regardless of whether the components in between reference the context at all — can call useContext to read the current value directly, bypassing every intermediate layer's props entirely.

flowchart TD
  P[Provider value=theme] --> A[Component A]
  P --> B[Component B]
  B --> C[Component C - useContext]
  P --> D[Component D - useContext]
Diagram source for Context & State Sharing.

What people get wrong

Context is a general-purpose global state management solution.
Context solves the specific problem of passing a value through many layers without prop drilling — it doesn't provide selective subscriptions, so every consumer re-renders on any change, which doesn't scale well to frequently-updating, complex application state. Reaching for context as the default state solution for everything leads to performance problems once the shared state updates often, because context lacks the fine-grained subscription that a dedicated state library provides.
Only the specific components reading the changed part of a context's value will re-render.
Every component calling useContext for that context re-renders when the Provider's value changes, even if the component only reads a field of the value object that didn't itself change. This is the most common surprise when a seemingly-unrelated part of the UI re-renders after an unrelated piece of context data updates, especially when the whole context value is one bundled object.
Passing an object literal directly as the value prop is harmless.
An inline object literal is a new reference on every render of the provider, defeating any memoization consumers rely on and causing unnecessary re-renders across the whole subtree. This is easy to write without noticing, because the object's actual field VALUES might be unchanged even though the object itself is a new reference each time.

When not to use it

The value changes very frequently (multiple times per second) and many components consume it.
A dedicated state management library with selective subscriptions, or restructure so the frequently-changing value lives closer to where it's actually used, rather than in a broad context.
The value only needs to reach one or two levels down.
Plain prop passing — context adds indirection that isn't worth it when the components in between genuinely need the value too, or when the tree is shallow.

Terms

Prop drilling
Passing a value through multiple components that don't use it themselves, purely to reach a deeply nested descendant that does.
Provider
The component that supplies a context's value to everything nested within it.
Consumer
Any component that reads a context's current value, typically via the useContext hook.
Context split
The practice of dividing data into multiple separate contexts based on how frequently each part changes, to limit unnecessary re-renders.

Can you recall it?

Why does wrapping a Provider's value in useMemo matter, and what specifically goes wrong without it?

Keep track of this

Add React Fundamentals to your map and Rungsy will schedule reviews so you actually remember it.