
Context API Re-render Optimization
Context is React’s built-in prop-passing alternative. Its re-render behavior is frequently misunderstood — every consumer re-renders whenever the context value changes, regardless of which part of the value they use.
The Re-render Problem
const UserContext = createContext();
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
return (
<UserContext.Provider value={{ user, theme, setUser, setTheme }}>
{children}
</UserContext.Provider>
);
} Every time theme changes, every component consuming UserContext re-renders — even if it only cares about user. Context uses reference equality on the value object, and a new object is created every render.
Fix 1: Split Contexts
Separate frequently-changing values into their own context:
const UserContext = createContext();
const ThemeContext = createContext();
function Providers({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
return (
<UserContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
</UserContext.Provider>
);
} Components subscribe to only what they need.
Fix 2: Memoize the Context Value
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(() => ({ user, setUser }), [user]);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
} The context value only changes when user changes. Note: setUser is already stable (state setters don’t change).
Fix 3: Separate State and Dispatch
Split into two contexts — one for state (changes), one for dispatch (stable):
const StateContext = createContext();
const DispatchContext = createContext();
function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<DispatchContext.Provider value={dispatch}>
<StateContext.Provider value={state}>
{children}
</StateContext.Provider>
</DispatchContext.Provider>
);
} dispatch is stable — components that only dispatch never re-render when state changes.
Fix 4: Memoize Derived Values
function useUserName() {
const { user } = useContext(UserContext);
return useMemo(() => user?.name, [user]);
} Does not prevent the context consumer re-render, but memoizes an expensive derivation. Useful when the derived computation is heavy.
When Context Is the Wrong Tool
Context re-renders all consumers on every value change. For high-frequency updates or many consumers, consider:
- Zustand — selector-based, rerenders only subscribers of changed slices
- Jotai — atom-based, extremely granular subscriptions
- Redux Toolkit — battle-tested,
reselectfor memoized selectors
Context excels for low-frequency updates: theme, locale, auth state, feature flags.









