context-rerenders-fix
Context Re-renders Fix
Diagnose and fix React Context re-renders through context splitting, memoization, or store extraction.
Why Context Causes Re-renders
Every component that calls useContext(MyContext) re-renders whenever the context value changes — even if the component only uses one field from a large object. This is the most common Context performance pitfall.
// ❌ Any change to user, theme, OR cart re-renders ALL consumers
const AppContext = createContext({ user, theme, cart, notifications });
Step 1: Diagnose
Before fixing, identify the actual problem. Ask the user or infer from their code:
Checklist — confirm these before proceeding:
- Which Context(s) are involved?
- How often does the context value change? (On every keystroke? Every route change? Only on login?)
- How many components consume this context?
- Are consumers close together (same subtree) or spread across the app?
- Is the value object recreated on every parent render?
Red flags to look for in code:
| Pattern | Problem |
|---|---|
value={{ user, theme, cart }} inline in JSX |
New object reference every render |
| Single context with 5+ unrelated fields | All consumers re-render on any field change |
useContext in a component that only uses 1 of 10 fields |
Unnecessary subscription |
| Context provider inside a frequently-updating parent | Value changes propagate constantly |
No useMemo wrapping the context value |
Object identity changes on every render |
Step 2: Choose the Right Fix
Use this decision tree:
Does the context value change frequently (e.g., on every keystroke or scroll)?
└─ YES → Is it avoidable? Can state be moved out of context?
└─ YES → Move high-frequency state to local state or URL state (not context)
└─ NO → Use useMemo + consider splitting
Does the context hold multiple unrelated concerns (user + theme + cart)?
└─ YES → SPLIT THE CONTEXT (Fix A)
Does the context hold one cohesive object that still causes re-renders?
└─ YES → MEMOIZE THE VALUE (Fix B)
Is the context very widely used across many subtrees with different needs?
└─ YES → EXTRACT TO EXTERNAL STORE (Fix C) — Zustand / Jotai
Are consumers doing expensive work on each render?
└─ YES → MEMOIZE CONSUMERS with React.memo / useMemo (Fix D)
Fix A: Split the Context
When: Single context mixes unrelated concerns. Splitting means components only subscribe to what they need.
// ❌ Before — one fat context
const AppContext = createContext<{
user: User;
theme: Theme;
cart: CartItem[];
setTheme: (t: Theme) => void;
addToCart: (item: CartItem) => void;
}>(null!);
// ✅ After — three focused contexts
const UserContext = createContext<User>(null!);
const ThemeContext = createContext<{ theme: Theme; setTheme: (t: Theme) => void }>(null!);
const CartContext = createContext<{ cart: CartItem[]; addToCart: (item: CartItem) => void }>(null!);
// Compose providers cleanly
export function AppProviders({ children }: { children: React.ReactNode }) {
return (
<UserProvider>
<ThemeProvider>
<CartProvider>{children}</CartProvider>
</ThemeProvider>
</UserProvider>
);
}
Result: A CartItem update only re-renders CartContext consumers — UserContext and ThemeContext consumers are unaffected.
Fix B: Memoize the Context Value
When: Context holds a cohesive object but the provider's parent re-renders frequently, creating a new object reference each time.
// ❌ Before — new object on every render of AuthProvider's parent
function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
return (
<AuthContext.Provider value={{ user, setUser }}>
{children}
</AuthContext.Provider>
);
}
// ✅ After — stable object reference, only changes when user changes
function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const value = useMemo(() => ({ user, setUser }), [user]);
// Note: setUser is stable (from useState), so [user] is the only real dep
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
Tip: Separate stable setters from changing values — see Fix A+B combined pattern in references/patterns.md.
Fix C: Extract to an External Store
When: Context is used across many unrelated subtrees, changes frequently, or you need fine-grained subscriptions.
Zustand and Jotai both use selector-based subscriptions — components only re-render when the slice they select changes.
// ❌ Before — Context causes all consumers to re-render on any field change
const useAppContext = () => useContext(AppContext);
const UserName = () => {
const { user } = useAppContext(); // re-renders when cart changes!
return <span>{user.name}</span>;
};
// ✅ After — Zustand with selector
import { create } from 'zustand';
interface AppStore {
user: User;
cart: CartItem[];
theme: Theme;
setTheme: (t: Theme) => void;
}
const useAppStore = create<AppStore>((set) => ({
user: initialUser,
cart: [],
theme: 'light',
setTheme: (theme) => set({ theme }),
}));
// Only re-renders when user changes — cart/theme changes are ignored
const UserName = () => {
const userName = useAppStore((state) => state.user.name);
return <span>{userName}</span>;
};
When to prefer Jotai: When state is naturally atomic and composed bottom-up (individual atoms vs a single store object).
See references/zustand-patterns.md for common Zustand migration patterns.
Fix D: Memoize Expensive Consumers
When: The context value must change frequently and splitting/memoizing the provider isn't enough — the consumer itself does expensive work.
// ✅ Wrap the consumer component in React.memo
// Re-renders only when its own props change, not when unrelated context changes
const ExpensiveList = React.memo(function ExpensiveList({ items }: { items: Item[] }) {
// ...expensive render
});
// ✅ Memoize derived values inside a consumer
function ProductGrid() {
const { products, filters } = useProductContext();
// Only recomputes when products or filters change
const filtered = useMemo(
() => products.filter((p) => matchesFilters(p, filters)),
[products, filters]
);
return <ExpensiveList items={filtered} />;
}
Caution: React.memo checks prop changes, not context changes. If the component calls useContext directly, it will still re-render on context change. Memoization helps most for children passed via props.
Step 3: Output Format
Always produce:
3a. Diagnosis Summary
- Which contexts are causing unnecessary re-renders
- Root cause (inline value object / fat context / high-frequency updates / no memoization)
- Estimated blast radius (how many components are affected)
3b. Fix Plan
- Which fix(es) to apply (A, B, C, D) and why
- Order of application (start with highest-impact)
3c. Before/After Code
- Show the problematic provider and at least one affected consumer
- Show the fixed version with comments explaining what changed and why
3d. Verification Steps
Tell the user how to confirm the fix worked:
1. Install React DevTools
2. Enable "Highlight updates when components render" in the Profiler
3. Trigger the state change that was causing the problem
4. Verify only the expected components highlight — not the whole tree
Or with the Profiler API:
<Profiler id="Cart" onRender={(id, phase, duration) => console.log(id, phase, duration)}>
<CartSection />
</Profiler>
Common Mistakes to Warn About
- Don't memoize everything preemptively.
useMemohas overhead — only apply it where re-renders are actually measured as problematic. useCallbackfor handlers in context value. If the context value includes functions, wrap them inuseCallbacktoo — otherwiseuseMemoon the object won't help since the function reference changes.- Context is not a replacement for a cache. If the data comes from an API, use React Query/SWR — don't fight re-renders caused by a pattern that shouldn't exist in the first place.
- Splitting context too aggressively creates provider hell. Aim for cohesion — split by domain concern, not by individual field.
Reference Files
references/patterns.md— Combined Fix A+B patterns, handler memoization, context selector patternreferences/zustand-patterns.md— Zustand migration recipes and selector patterns
Read these when the user's situation requires more detailed patterns than the examples above.
More from blunotech-dev/agents
anti-purple-ui
Enforce a strict monochrome UI with a single high-contrast accent color, removing generic tech gradients and “AI-style” palettes. Use when the user wants minimal, anti-AI, or non-generic aesthetics, or says the UI looks too techy or generic.
9harmonize-whitespace
Align all spacing (padding, margins, gaps) to a consistent 4pt/8pt grid. Use when spacing feels off, inconsistent, cramped, or unbalanced, or when the user asks for a spacing scale or alignment fix.
9typographic-hierarchy
Improve typography by adjusting font sizes, weights, spacing, and contrast to create clear visual hierarchy and readability. Use when text feels flat, unstructured, or when the user asks to refine headings, type scale, or overall readability.
6micro-interaction-adder
Add polished CSS micro-interactions like hover effects, transitions, and feedback states to improve UI feel. Use when the user asks for animations, better UX, or when the interface feels static, plain, or unresponsive.
4consistent-border-radius
Normalizes rounded corners across a file so buttons, inputs, cards, modals, badges, and all UI elements share the exact same curvature. Use this skill whenever the user mentions inconsistent border radii, wants to unify rounded corners, asks to make UI elements look more cohesive, or says things like "make the corners match", "fix the rounding", "unify border radius", "standardize my rounded corners", or "buttons and cards don't match". Also trigger when the user pastes a CSS/HTML/JSX/TSX file and asks for a design consistency pass, border radius is one of the first things to normalize.
4component-split
Analyze a component and determine when and how to split it based on size, responsibility, and reuse signals, producing a refactored structure with clear boundaries. Use when users share large, mixed-concern, or hard-to-test components, or ask about splitting, refactoring, or improving component architecture.
3