Chapter 3 of 4
Context and Re-renders
The one performance trap everyone hits, and how to avoid it.
Every component that reads a context re-renders when the provider's value changes - and React compares that value by reference.
The classic mistake
function App() {
const [user, setUser] = useState(null);
// A brand new object on every render of App
return (
<UserContext.Provider value={{ user, setUser }}>
<Everything />
</UserContext.Provider>
);
}The fix is to memoise the object so its identity is stable while its contents are.
function App() {
const [user, setUser] = useState(null);
const value = useMemo(() => ({ user, setUser }), [user]);
return (
<UserContext.Provider value={value}>
<Everything />
</UserContext.Provider>
);
}Split contexts that change at different rates
If one part of the value changes constantly and another rarely, put them in separate contexts. Components that only need the stable half stop re-rendering.
// Changes on every keystroke
<SearchQueryContext.Provider value={query}>
{/* Changes almost never */}
<SearchActionsContext.Provider value={actions}>
<Results />
</SearchActionsContext.Provider>
</SearchQueryContext.Provider>Keep the provider high and the tree below it stable
Children passed to a provider as children are created by the parent, so they do not re-render just because the provider's value changed - only the consumers do.
Memoise a context value by hand
Complete makeValueCache so it returns the same object while the user id is unchanged, and a new object when it changes.