
Context API Performance: Why You Should Split Your State
Put everything in one `UserContext`? Bad move. Learn how a single update re-renders the entire app and how to optimize using Context Splitting and Selectors.

Put everything in one `UserContext`? Bad move. Learn how a single update re-renders the entire app and how to optimize using Context Splitting and Selectors.
Establishing TCP connection is expensive. Reuse it for multiple requests.

Optimizing by gut feeling made my app slower. Learn to use Performance profiler to find real bottlenecks and fix what matters.

Definitely No, Maybe Yes. How to check membership in massive datasets with minimal memory using Bit Arrays and Hash Functions. False Positives explained.

Text to Binary (HTTP/2), TCP to UDP (HTTP/3). From single-file queueing to parallel processing. Google's QUIC protocol story.

Early in the project, I hated the Redux boilerplate, so I decided to use React's built-in Context API.
I created a single AppStateContext and dumped everything in it: user info, theme, modal state, notification list, etc.
const AppStateContext = createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [modal, setModal] = useState(false);
// 😱 Worst Code: Bundling all state into one object
const value = { user, setUser, theme, setTheme, modal, setModal };
return <AppStateContext.Provider value={value}>{children}</AppStateContext.Provider>;
}
It was convenient. Just useContext(AppStateContext) and I could grab data anywhere.
But as the app grew, problems exploded.
"I switched to Dark Mode (setTheme), why did my Signup Form clear?"
"I opened a modal, why did the entire page flash?"
I mistakenly thought Context "subscribes only to necessary data."
I thought const { theme } = useContext(AppState) meant it would only re-render when theme changes.
But React Context's mechanism is brutally simple. "If the Provider's value changes, force re-render ALL components subscribing to it."
Because I was creating a new value object ({...}) every time, even if only theme changed, the value object's reference changed.
Then LoginForm, Header, Sidebar—everyone subscribing to value—thought "Oh? Data changed?" and re-rendered.
I understood this when I compared it to a "Village PA System."
Putting everything in one Context is like "Broadcasting every trivial gossip via one loudspeaker 24/7."
The core usage of Context API optimization is "Separation of Concerns." Unrelated states must reside in different Contexts.
The most common pattern is separating "Value" and "Function (Setter)."
Values change often, but functions (setUser) don't change throughout the component lifecycle.
export const UserStateContext = createContext(null);
export const UserDispatchContext = createContext(null);
function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
// Only re-renders here if value changes
<UserStateContext.Provider value={user}>
{/* Functions don't change, so this is safe */}
<UserDispatchContext.Provider value={setUser}>
{children}
</UserDispatchContext.Provider>
</UserStateContext.Provider>
);
}
Now, child components that only need setUser (like a login button) subscribe only to UserDispatchContext.
So even if user state changes (login success), the button component doesn't re-render!
ThemeContext, ModalContext, UserContext...
Even if it's annoying, split the files.
ThemeContext: Light/Dark mode (Global impact)FormContext: State used only within a specific page (Local)"Doesn't too many Contexts cause Provider Hell?" Yes, it looks ugly. But for performance, this is the right way.
Instead of using Context directly, it's better to encapsulate internals with Custom Hooks.
// Hooks/useUser.ts
export function useUser() {
const state = useContext(UserStateContext);
if (!state) throw new Error('Cannot find UserProvider');
return state;
}
export function useUserDispatch() {
const dispatch = useContext(UserDispatchContext);
if (!dispatch) throw new Error('Cannot find UserProvider');
return dispatch;
}
This way, components don't need to know useContext.
Also, if you later switch from Context to Redux or Recoil, you don't need to modify component code. Just change the hook internals.
Even with split Contexts, if the Provider's parent re-renders, child components might also re-render.
Wrap components with React.memo.
"Re-renders caused by Context updates" pierce through React.memo,
but "Re-renders caused by Parent updates" are blocked by React.memo.
Context API:
Zustand / Recoil / Redux:
useStore(state => state.bears)). Re-renders only when the data I want changes.Putting input forms or realtime chart data into Context is suicide. Use Zustand or Recoil for that.
This problem became dramatic when I built a Chat App.
I put messages (chat list) and typingUsers (people typing) in the same ChatContext.
Every time someone typed, typingUsers changed (5 times a second).
Every time, the entire message list (thousands of items) subscribing to ChatContext re-rendered.
Result: Horrible lag every time I typed.
useChatStore(state => state.typingUsers).TypingContext.Typing lag disappeared completely. Lesson: Never mix "High Frequency" and "Low Frequency" values.
"I want to SEE where re-renders happen." Open the Profiler tab in Chrome extension React DevTools.
It tells you exactly which component rendered and "Why did this render?" (Reason: Hook 1 changed). The best tool to catch the culprit.
useMemo caches values, useCallback caches functions.Q: Can I use multiple Contexts in one component?
A: Yes. A component can consume UserContext AND ThemeContext. It will re-render if either changes.
Q: Should I wrap everything in useMemo?
A: Inside the Provider? Yes. The value prop should almost always be memoized. Inside the Consumer? Only if the computation is expensive.
Q: Is Context slow?
A: No, Context itself is fast. What's slow is Re-rendering the entire subtree. If you put context at the very top (index.js) and update it often, you force React to diff the entire DOM tree repeatedly.
Q: What about useReducer?
A: useReducer + Context is a powerful combo that mimics Redux. You pass the dispatch function down the context. This works great for complex state logic, but it still suffers from the same re-rendering optimization issues as plain useState if you don't split the contexts.
Context is for Dependency Injection (DI), not High-Performance State Management.