Detailed explanation
To avoid unnecessary re-renders from React context, reduce both how often a provider value changes and how many components subscribe to it. React compares the previous and next provider values using Object.is. When that comparison detects a change, React schedules updates for components reading that context, even if they only use an unchanged property. This matters because a single large context containing authentication, theme, notifications, and form input can turn a small update into work across many unrelated consumers.
First, split contexts by concern and update frequency. For example, use separate AuthContext and ThemeContext so changing the theme does not trigger authentication consumers through context propagation. When useful, also separate state from actions: a component that only needs a stable reducer dispatch function can subscribe to an actions context without subscribing to changing state. Keep rapidly changing values, such as individual input characters or pointer coordinates, local or scoped to a small subtree rather than placing them in an application-wide provider. Choose boundaries around actual consumption patterns, not simply one context per field.
Second, stabilise the provider value when its contents have not changed. An inline object creates a new reference on every provider render, so use const value = useMemo(() => ({ user, logout }), [user, logout]); and pass value to AuthCtx.Provider. If logout is declared inside the provider, useCallback can stabilise it with complete dependencies; otherwise, a newly created function defeats the object memoisation. This does not prevent legitimate updates when user changes, and it does not stop re-renders caused by consumers’ own state or ordinary parent rendering.
Finally, understand the limits of component memoisation. React.memo does not block an update from context that the component itself reads. You can instead read context in a small wrapper and pass selected, stable props to a memoised expensive child. For fine-grained subscriptions, consider a context-selector library or an external store with selector support; standard useContext does not select individual fields. Interviewers look for this distinction between value identity, subscription scope, and rendering cost. Confirm the bottleneck with React DevTools Profiler before adding complexity.
Key points to remember
- Context changes are detected with Object.is, not deep comparison.
- Split contexts by concern, update frequency, and consumer needs.
- Memoise object provider values and stabilise function dependencies where appropriate.
- Keep high-frequency state local or inside narrowly scoped providers.
- React.memo cannot block a component’s own context updates; use subscription boundaries or selectors.
Common mistakes candidates make
- Assuming that destructuring one field from useContext subscribes only to that field.
- Using useMemo while recreating a function dependency on every render, or omitting dependencies to force stability.
- Expecting provider memoisation to stop all parent-driven renders, or mutating the value in place to avoid context updates.
Likely follow-up questions
Does React.memo prevent a context consumer from re-rendering?
No, a component that reads a changed context still receives an update even when its props are unchanged. A small context-reading wrapper can pass a selected primitive or stable reference to a memoised child, allowing that child to skip unrelated updates.
Why separate state and dispatch into different contexts?
The dispatch function returned by useReducer has stable identity, while the state changes after updates. Components subscribing only to the dispatch context therefore avoid context-triggered updates from state changes, although other rendering causes still apply.
When should you use selectors instead of standard context?
Selectors help when many consumers need different slices of frequently changing shared state. Use a selector-capable store or context library that compares selected results; merely selecting a field after calling useContext does not narrow the subscription.
Interview tip: Explain Object.is first, show the memoised provider value, and then clarify why splitting subscriptions matters more than wrapping every consumer in React.memo.
