- Which leads to the Provider and all its descendants re-rendering anyways
While this is the default behavior, in practice it's common to change this in order to improve performance. Pure components, components that implement shouldComponentUpdate, or components using React.memo will cause the rerendering to stop before going through the entire tree.
For example: suppose there's a toplevel component with some state, which renders a midlevel component that has shouldComponentUpdate() { return false; }
, which renders a bottom level component.
function TopLevelComponent() {
const [topLevelState, setTopLevelState] = useState(0);
return (
<>
<h1>Top Level Component</h1>
<button onClick={setTopLevelState( v => v + 1)}>Update Top Level State</button>
<MidLevelComponent />
</>
);
}
class MidLevelComponent extends React.Component {
shouldComponentUpdate() {
return false; // <= This guy will prevent re-rendering of this component and everything nested under it
}
render() {
return (
<>
<h2>Mid Level Component</h2>
<BottomLevelComponent />
</>
);
}
}
function BottomLevelComponent() {
return "Bottom Level";
}
On the initial mount, all 3 of these will render. But then if the toplevel component updates its state, only the toplevel component will rerender. The midlevel component will be skipped due to its shouldComponentUpdate, and then the bottom level component is never even considered. (See live code snippet below -better run in Full Page mode)
console.log("--- Initial Render");
function BottomLevelComponent() {
console.log("BottomLevelComponent() => renders");
return "Bottom Level";
}
class MidLevelComponent extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
console.log("MidLevelComponent() => renders");
return (
<div>
<h2>Mid Level Component</h2>
<BottomLevelComponent />
</div>
);
}
}
function TopLevelComponent() {
console.log("TopLevelComponent() => renders");
const [topLevelState, setTopLevelState] = React.useState(0);
const handleTopLevelUpdate = () => {
console.log("--- Updating Top Level State");
setTopLevelState((v) => v + 1);
};
return (
<div>
<h1>Top Level Component</h1>
<button onClick={handleTopLevelUpdate}>Update Top Level State</button>
<MidLevelComponent />
</div>
);
}
ReactDOM.render(<TopLevelComponent />, document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Now we add in a context provider to the toplevel component, and a context consumer to the bottom level component. On the initial mount, they will again all render. If the toplevel component updates its state, it will rerender. The midlevel component will still skip its render, due to its shouldComponentUpdate. But as long as the context value changed, the bottom level component will rerender, even though its parent bailed out. This is the feature that is referred to by that blurb.
const TopLevelContext = React.createContext();
export default function TopLevelComponent() {
const [topLevelState, setTopLevelState] = useState(0);
return (
<TopLevelContext.Provider value={{ topLevelState }}>
<h1 onClick={setTopLevelState((v) => v + 1)}>Top Level Component</h1>
<MidLevelComponent />
</TopLevelContext.Provider>
);
}
class MidLevelComponent extends React.Component {
shouldComponentUpdate() {
return false; // <= Will prevent rendering of this Component and everything nested under it, but...
}
render() {
return (
<>
<h2>Mid Level Component</h2>
<BottomLevelComponent />
</>
);
}
}
function BottomLevelComponent() {
React.useContext(TopLevelContext); // <= ...this will override the shouldComponentUpdate of the parent and trigger a re-render when the Context provider value changes
return "Bottom Level";
}
console.log("--- Initial Render");
const TopLevelContext = React.createContext();
function BottomLevelComponent() {
React.useContext(TopLevelContext);
console.log("BottomLevelComponent() => renders");
return "Bottom Level";
}
class MidLevelComponent extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
console.log("MidLevelComponent() => renders");
return (
<div>
<h2>Mid Level Component</h2>
<BottomLevelComponent />
</div>
);
}
}
function TopLevelComponent() {
console.log("TopLevelComponent() => renders");
const [topLevelState, setTopLevelState] = React.useState(0);
const handleTopLevelUpdate = () => {
console.log("--- Updating Top Level State");
setTopLevelState((v) => v + 1);
};
return (
<TopLevelContext.Provider value={{ topLevelState }}>
<h1>Top Level Component</h1>
<button onClick={handleTopLevelUpdate}>Update Top Level State</button>
<MidLevelComponent />
</TopLevelContext.Provider>
);
}
ReactDOM.render(<TopLevelComponent />, document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<div id="root"></div>