There is no render bailout for context consumers (v17
).
Here is a demonstration, where the Consumer
will always re-render just because he is a Context Consumer, even though he doesn’t consume anything.
import React, { useState, useContext, useMemo } from "react";
import ReactDOM from "react-dom";
// People wonder why the component gets render although the used value didn't change
const Context = React.createContext();
const Provider = ({ children }) => {
const [counter, setCounter] = useState(0);
const value = useMemo(() => {
const count = () => setCounter(p => p + 1);
return [counter, count];
}, [counter]);
return <Context.Provider value={value}>{children}</Context.Provider>;
};
const Consumer = React.memo(() => {
useContext(Context);
console.log("rendered");
return <>Consumer</>;
});
const ContextChanger = () => {
const [, count] = useContext(Context);
return <button onClick={count}>Count</button>;
};
const App = () => {
return (
<Provider>
<Consumer />
<ContextChanger />
</Provider>
);
};
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
To fix it:
- Use a single context for each consumed value. Meaning that context holds a single value, (and no, there is no problem with multiple contexts in an application).
- Use a state management solution like Recoil.js, Redux, MobX, etc. (although it might be overkill, think good about app design beforehand).
- Minor optimization can be achieved by memoizing Provider’s values with
useMemo
.