Frontend
The re-render you don't see coming from context providers
A context value that changes identity every render silently re-renders every consumer, even ones that don't look like they depend on it.
Last updated September 17, 2026
A common performance bug that doesn't look like a bug at all: a context provider that re-renders every single consumer on every parent render, even consumers that only read a field that never actually changed.
The usual cause
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState("dark");
return (
<AppContext.Provider value={{ user, setUser, theme, setTheme }}>
{children}
</AppContext.Provider>
);
}
That object literal in value is a new object on every render of AppProvider, regardless of whether user or theme actually changed. React's context system compares the value by identity, not by deep equality — a new object identity means every consumer re-renders, full stop, even a component that only reads theme and doesn't care that user just changed.
Why this is worse than it sounds
The reason this bug is easy to miss: it doesn't look wrong in the component reading theme. That component still renders correctly, with the right value, every time — it's just re-rendering far more often than it needs to, silently, with no visible symptom unless you're specifically profiling render counts. A user update three levels up in state triggers a re-render cascade through every consumer of the shared context, including ones with zero actual dependency on user.
useMemo helps, but only sometimes
const value = useMemo(
() => ({ user, setUser, theme, setTheme }),
[user, theme]
);
This stabilizes the object's identity across renders where user and theme haven't changed, which cuts out re-renders from unrelated parent re-renders. It doesn't fix the deeper issue: when user genuinely does change, every consumer of this context still re-renders, including the ones that only read theme — because they're both bundled into the same context value, and there's no way to subscribe to just one field of it.
Splitting the context is the actual fix
At real scale, the fix is separating concerns that change independently into separate contexts:
<UserContext.Provider value={userValue}>
<ThemeContext.Provider value={themeValue}>
{children}
</ThemeContext.Provider>
</UserContext.Provider>
Now a theme consumer only re-renders when ThemeContext's value changes, completely insulated from user updates. This is more boilerplate than one combined provider, and it's the difference between a context architecture that scales and one that quietly re-renders half the app on every unrelated state change, in a way that's genuinely hard to spot without a profiler specifically looking for it.
Tags
Related posts