Why React Context Can Cause Re-renders?
Whenever a Context Provider’s value changes, all components consuming that context re-render, even if only part of the value changed or if a specific consumer didn’t use that part.
Example:
const AppContext = React.createContext();
function App() {
const [user, setUser] = useState({ name: "Dev" });
const [theme, setTheme] = useState("light");
return (
<AppContext.Provider value={{ user, theme }}>
<UserProfile />
<ThemeSwitcher />
</AppContext.Provider>
);
}
👉 Even if you change only the theme, UserProfile will still re-render — because the entire context value object changed.
⚙️ 1. Split Contexts
Instead of using one big context for everything, split them by concern.
const UserContext = React.createContext();
const ThemeContext = React.createContext();
function App() {
const [user, setUser] = useState({ name: "Dev" });
const [theme, setTheme] = useState("light");
return (
<UserContext.Provider value={user}>
<ThemeContext.Provider value={{ theme, setTheme }}>
<UserProfile />
<ThemeSwitcher />
</ThemeContext.Provider>
</UserContext.Provider>
);
}
✅ Now changing the theme won’t cause UserProfile to re-render.
⚙️ 2. Memoize the Context Value
When you pass an object or function in the context, wrap it with useMemo so it only changes when necessary.
const ThemeContext = React.createContext();
function App() {
const [theme, setTheme] = useState("light");
const value = React.useMemo(() => ({ theme, setTheme }), [theme]);
return (
<ThemeContext.Provider value={value}>
<Toolbar />
</ThemeContext.Provider>
);
}
✅ This prevents unnecessary new object references on each render.
⚙️ 3. Use Selectors with Libraries
React itself doesn’t support context “selectors” out of the box, but libraries like use-context-selector or Zustand allow consumers to subscribe to only specific parts of the context.
npm install use-context-selector
Example:
import { createContext, useContextSelector } from "use-context-selector";
const AppContext = createContext();
function UserName() {
const name = useContextSelector(AppContext, v => v.user.name);
return <p>{name}</p>;
}
✅ Only re-renders when user.name changes — not when other context values do.
⚙️ 4. Move State Down the Tree
If a state is only needed by a subset of components, don’t put it in global context unnecessarily — keep it closer to where it’s used.
// ❌ Overuse of context
<AuthContext.Provider value={isLoggedIn}>
<Header />
</AuthContext.Provider>
// ✅ Localize state
<Header isLoggedIn={isLoggedIn} />
⚙️ 5. Use React.memo for Consumers
If your component uses context but also has props, wrap it in React.memo so it doesn’t re-render unless needed.
const UserProfile = React.memo(function UserProfile() {
const user = useContext(UserContext);
return <div>{user.name}</div>;
});
⚙️ 6. Avoid Inline Functions or Objects as Context Values
Bad:
<ThemeContext.Provider value={{ theme, toggle: () => setTheme(t => !t) }}>
Good:
const toggle = useCallback(() => setTheme(t => !t), []);
const value = useMemo(() => ({ theme, toggle }), [theme]);
<ThemeContext.Provider value={value}>
✅ Prevents a new object each render.
🧱 Summary
| Optimization | Why It Helps |
|---|---|
| Split large contexts | Limits re-render scope |
| Memoize context values | Prevents new object refs |
| Use selector libraries | Consumers re-render only for relevant changes |
| Move state down | Reduces global dependency |
Use React.memo |
Avoids re-render for same props |
Use stable functions (useCallback) |
Keeps value reference consistent |
In short:
To optimize React Context performance, minimize the frequency and scope of context value changes using techniques like splitting contexts, memoization, and selectors — so only the necessary components re-render.