When a React context provider is rendered with a new value, which components re-render because of that context change — every descendant of the provider, or only the ones that read the context?
answer
- readers, not descendants
- two separate causes, same symptom
- memo compares props only
- depth does not matter for propagation
- Object.is on the value you passed
basics
~20 sOnly components that read that context re-render because of the change. React notifies the consumers it finds below the provider. Other descendants re-render for a different reason: their own parent re-rendered and re-created their elements.
solid answer
~40 sA context change propagates only to consumers — components that call `useContext(MyContext)` or `use(MyContext)`. React compares the old and new value with `Object.is`; if they differ, it schedules work on every consumer beneath that provider, no matter how deep. Non-consuming descendants are untouched by that mechanism. What confuses people is that in practice the whole subtree often does re-render — but that is the ordinary cause: the component holding the state re-rendered, so it re-created its children's elements. Two separate causes with the same symptom. It also means `React.memo` does not protect a consumer: memo compares props, and a context read is not a prop, so a memoized consumer still re-renders when the value changes.
go deeper
Be able to say plainly that a context update re-renders the components that read the context, at any depth, and that React decides by comparing the value it was given.
Explain the two independent causes — provider value identity versus the owning component re-rendering — and why React.memo blocks the second but not the first.
Show how you diagnose which cause is firing in a real app before changing anything, and pick the matching fix rather than sprinkling memo across the subtree.
Frame it as an architecture question: where state lives determines how much of the tree is coupled to it, and a provider high in the tree is a decision about blast radius, not a rendering detail.
## The two independent reasons a component under a provider re-renders When you look at a React app and see "everything under the provider re-rendered", there are two different mechanisms that can produce that, and interviewers ask this question to see whether you can tell them apart. **Cause 1 — normal parent-to-child rendering.** The component that owns the state and renders the provider re-rendered. Rendering a component re-runs its function body, which re-creates the JSX elements it returns, which means React renders its children too (unless something bails out). This has nothing to do with context; it would happen with plain props. **Cause 2 — context propagation.** The provider was given a value that is not `Object.is`-equal to the previous one. React then walks the tree below that provider and schedules an update on each fiber that reads this particular context. Only readers are affected — a `<div>`, a layout wrapper, or any component that never calls `useContext` for that context is not a target of this mechanism. A reader is a component that calls `useContext(MyContext)` or, in React 19, `use(MyContext)`. The legacy `MyContext.Consumer` render-prop form and a class component's `contextType` also count as readers, though you rarely write them today. ## Why depth does not matter Context propagation is not parent-to-child hand-off. React does not need each intermediate component to re-render for a deeply nested consumer to receive the new value. That is the whole point of context: a consumer twenty levels down gets the update directly, even if every component in between bails out of rendering. This is also why `React.memo` is not a shield. `React.memo` makes a component skip re-rendering when its props are shallowly equal to last time. A context read is not a prop, so React marks the memoized component as needing work anyway: ```jsx const Badge = React.memo(function Badge() { const { user } = useContext(AuthContext); // still re-renders on value change return <span>{user.name}</span>; }); ``` What `React.memo` *can* do is stop **cause 1** from travelling through it. If a memoized component takes stable props and does *not* read the context, it bails out, and its own children are not re-rendered by the parent's render — even though a consumer further down still gets its context update through cause 2. ## Why the distinction is practical, not academic The fix you reach for depends on which cause you are looking at. If consumers re-render because the **value identity** keeps changing (a fresh object literal on every parent render), the fix lives at the provider: memoize the value, or split it up. If the subtree re-renders because the **provider's owner** re-renders, memoizing the value changes nothing about that subtree — you address it by not re-creating those children, for example by having the provider component render `children` it received as a prop rather than JSX it constructs itself. A quick way to check which one you are seeing: comment out the `useContext` call in a component that is re-rendering. If it still re-renders, you are looking at cause 1 and the provider value is not your problem. ## What "the value changed" means React's comparison is `Object.is` on the value you passed. Two structurally identical objects are different values: ```jsx // new object every render — every consumer re-renders every time <ThemeContext value={{ theme, toggle }}>{children}</ThemeContext> ``` A primitive value behaves the way people intuitively expect: passing the string `"dark"` twice in a row is the same value, so consumers are not notified. It is object and function values — the common case, because a context usually carries state plus updaters — that make this a pitfall. ## In React 19 You can render the context object directly as the provider, `<ThemeContext value={...}>`; `<ThemeContext.Provider value={...}>` still works and behaves identically. Reading with `use(ThemeContext)` is allowed inside conditionals and loops, unlike `useContext`. None of that changes the propagation model described above. ## Saying it in an interview "Context updates target readers, not descendants. React compares the value with `Object.is` and schedules work on every component that reads that context below the provider, at any depth, and `React.memo` doesn't stop it because a context read isn't a prop. If non-readers are also re-rendering, that's the ordinary parent-re-render path, which is a separate thing to fix."
- If React.memo cannot stop a consumer from re-rendering, when is it still worth putting on a component inside a provider's subtree?It still blocks the ordinary parent-render path. A memoized component with stable props that does *not* read the context will bail out, so it and its non-consuming children skip rendering when the provider's owner re-renders. It just cannot block the context mechanism for a component that actually reads the context.
- How would you tell, in a real app, whether a component is re-rendering because of the context or because its parent re-rendered?Remove or comment out the `useContext` call and see if it still re-renders — if it does, it is the parent path. The React DevTools Profiler is the non-invasive version: it labels why each component rendered, distinguishing a parent render from a context change.
- Does an intermediate component between the provider and a consumer have to re-render for the consumer to get the new value?No. React schedules work directly on the consuming fibers, so the update reaches a deeply nested consumer even if every component in between bails out. That direct delivery is exactly what context exists to provide.
saying these in an interview costs you the question
- Says every descendant of the provider re-renders
- Thinks React.memo prevents context-driven re-renders
- Believes the value has to be handed down through each level
- Assumes context does a deep comparison of the value
- Confuses a parent re-render with a context update