I think the general rule is: the context consumers only re-render when the context value has changed. The rule:
https://reactjs.org/docs/context.html#contextprovider
All consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes. The propagation from Provider to its descendant consumers (including .contextType and useContext) is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component skips an update.
Changes are determined by comparing the new and old values using the same algorithm as Object.is.
However, on this sample app, https://codesandbox.io/s/loving-wood-dzqm9g (to be able to see the app running, you may need to bring it to its own window: https://dzqm9g.csb.app/ ), every component re-renders regardless the context value has changed or not.
The first Context.Provider
actually changes the value:
<ThemeContext.Provider
value={toggle ? themeColors.light : themeColors.dark}
>
so it is reasonable that the consumer underneath it is re-rendered. But the <TheTimeNow />
is not a consumer, and is re-rendered for some reason. The time is updated in the box every time the Toggle button is pressed.
And even the second Context.Provider
:
<ThemeContext.Provider value={themeColors.light}>
<div
className="App"
style={{ border: "3px dotted #07f", margin: "18px" }}
>
<Container />
</div>
<TheTimeNow />
</ThemeContext.Provider>
The context value does not change at all, yet all the components are re-rendered.
To make it one step further, I outright provided a constant context value and use only one context provider:
https://codesandbox.io/s/cranky-sunset-3sbhr4?file=/src/App.js
And I am still able to make every component re-render, as we can see the time updated in every component.
So is it against the rule mentioned at the top of this post? Or is it true that we also have this rule: every children component re-renders when the parent re-renders (and therefore the whole subtree re-renders ? So because <App />
re-renders, everything underneath re-renders?
So when both rules apply, then it ended up all children in the subtree re-renders.
However, how would we make a <Context.Provider>
change its value, but have this container component not re-render? I cannot think of a case because we usually use a state or props to cause the context value to change and re-render this component, so the whole subtree will re-render. How is it possible the value change but this container doesn't re-render?
In other words, for <ThemeContext.Provider value={someValue}>
to give a different value, this <ThemeContext.Provider>
component must be rendering and therefore, the component containing this line is also re-rendering, and so all children, and even the whole subtree would re-render. Then why would we say, "only the context consumers will re-render"?