Context: Power & Pitfalls
โฑ๏ธ ~3-minute bite ยท solve the sandbox to master
5-Year-Old Metaphor
โ The physical, real-world picture. No jargon.๐ป Context = broadcast radio. Everyone in range receives it. Split contexts = separate stations โ subscribe only to the one you care about. External store = cable TV โ precise channel subscriptions.
One Big Context
AM radio
Everyone hears everything. Cart update โ every consumer re-renders, even those showing only the theme.
Split Contexts
Multiple stations
ThemeContext, UserContext, CartContext. Each consumer subscribes only to the station it needs.
External Store
Cable with channels
Selector-based subscriptions. CartCount subscribes only to `s.items.length` โ nothing else triggers it.
Context is not a state management library
Context solves prop drilling โ passing values through many component layers. It does not solve re-render optimization. For fine-grained subscriptions to frequently-changing state, use Zustand, Jotai, or useSyncExternalStore directly.
Interactive Sandbox
โ Move something, see it react instantly.Pattern
Scenario
Sharing auth user, theme, and locale across the component tree without prop drilling
Re-renders
0| 1 | // 1. Create context with a typed default |
| 2 | const UserContext = createContext<User | null>(null); |
| 3 | ย |
| 4 | // 2. Custom hook for safe consumption |
| 5 | function useUser() { |
| 6 | const ctx = useContext(UserContext); |
| 7 | if (!ctx) throw new Error('useUser must be inside UserProvider'); |
| 8 | return ctx; |
| 9 | } |
| 10 | ย |
| 11 | // 3. Provider wraps the subtree |
| 12 | function UserProvider({ children }: { children: ReactNode }) { |
| 13 | const [user, setUser] = useState<User>(currentUser); |
| 14 | return ( |
| 15 | <UserContext.Provider value={user}> |
| 16 | {children} |
| 17 | </UserContext.Provider> |
| 18 | ); |
| 19 | } |
| 20 | ย |
| 21 | // 4. Any descendant accesses user โ no prop drilling |
| 22 | function Avatar() { |
| 23 | const user = useUser(); |
| 24 | return <img src={user.avatarUrl} alt={user.name} />; |
| 25 | } |
Challenge
Trace the re-render cascade for each pattern. Can you explain why the count changes?
Why Should I Care?
โ The exact interview question + the bug it kills.Interview questions
Q: Why does context not prevent re-renders?
`useContext` subscribes to the context. When the Provider's value prop changes, every component that called `useContext(SomeCtx)` re-renders. There is no selector system โ you get the whole value or nothing. The only way to reduce re-renders is to split contexts, memoize the value, or use an external store with selectors.
Q: When should you NOT use context for state?
Don't use context for: frequently-updating state (more than a few times per second), state with many independent pieces that consumers partially use, server state (use TanStack Query), or client state with complex update logic. Use context for: theme, locale, auth, feature flags โ values that rarely change and all consumers care about equally.
Q: What is useSyncExternalStore and why was it added?
| 1 | // useSyncExternalStore โ React 18 hook for external stores |
| 2 | // Used by Redux, Zustand, Jotai under the hood |
| 3 | const value = useSyncExternalStore( |
| 4 | store.subscribe, // subscribe function |
| 5 | store.getSnapshot, // get current value (client) |
| 6 | store.getServerSnapshot // get value for SSR |
| 7 | ); |
| 8 | ย |
| 9 | // Problem it solves: "tearing" in Concurrent Mode |
| 10 | // Without it, different components in one render could |
| 11 | // read different snapshots of an external store |
| 12 | // โ UI shows inconsistent state simultaneously |
| 13 | // useSyncExternalStore gives React a consistent snapshot |
The Deep Dive
โ Spec refs, engine internals, the minutiae.React context implementation
Context is implemented as a linked list of "consumers" on the fiber node. When a Provider renders with a new value, React traverses the fiber tree from the Provider downward, finding all fibers that have a `readContext(SomeCtx)` call. Those fibers are marked for re-render. This traversal is the source of the "all consumers re-render" behavior โ React has no way to know which part of the context value each consumer uses.
Context selector pattern (react-context-selector)
The library `use-context-selector` provides a `useContextSelector(ctx, selector)` hook that only triggers re-render when the selected value changes. It achieves this by storing all subscribers in a ref and calling each selector after context updates, comparing old/new โ only re-rendering if the selected value changed. React itself may adopt this pattern natively in a future release.
Zustand subscription model
Zustand uses `useSyncExternalStore` internally. The selector passed to `useStore(selector)` is called after every store update. If `Object.is(prevSelected, nextSelected)` returns true, the component doesn't re-render. This is why `useStore(s => s.items.length)` only re-renders when length changes โ even if individual items are added or modified without changing the count.
Interview Questions
โ Real questions from real interviews โ with answers.useContext subscribes to the entire context value โ any change to any field re-renders all consumers.
Every component that called useContext(Ctx) re-renders when the context value changes โ split contexts to limit who's subscribed.
Passing an object literal `value={{ user, logout }}` to the Provider creates a new object on every parent render.
Don't use Context for frequently-changing state, partially-subscribed state, server state, or complex update logic.
It gives React a consistent store snapshot to prevent 'tearing' โ UI inconsistency when Concurrent Mode pauses mid-render.
It stores all subscriber selectors in a ref, runs them after every context update, and only re-renders when the selected value changed.
Memory Game
โ Quick quiz โ lock the concept in long-term memory.What risk does a very deeply nested context Provider introduce?