Re-render Optimization Demo
Advanced - React Compiler Showcase
Side-by-side comparison of single context vs. split contexts. Watch the render count badges to see the difference!
Live Comparison
⚠️ Important Discovery: With a single context containing all values, ALL consumers re-render when ANY value changes - even if they only use stable parts like actions.
Solution: Split contexts allow components to subscribe to exactly what they need.
❌ Single Context (Current Pattern)
• Display: Re-renders ✅ (uses state)
• Buttons: Re-renders ✅ (uses state for validation)
• Reset: Re-renders ❌ (only uses actions, but context value changes!)
✅ Split Contexts (Optimized Pattern)
• Display: Re-renders ✅ (subscribes to StateContext)
• Buttons: Re-renders ✅ (subscribe to StateContext for validation)
• Reset: NEVER re-renders ✅ (only subscribes to ActionsContext!)
Try it: Click + or - rapidly and watch the render count badges. Notice how the Reset button on the RIGHT never increments!
Why This Happens
Problem with Single Context
// Single context with all values
<CounterContext.Provider
value={{
state: { count }, // Changes frequently ⚠️
actions: { increment }, // Stable (never changes)
meta: { min, max } // Stable (never changes)
}}
>
// Even though 'actions' never change, the OBJECT changes!
// Every render creates NEW object → ALL consumers re-renderThe context value is a new object on every render, even though actions and meta are stable. This forces ALL consumers to re-render, even the Reset button that only uses actions.
Solution: Split Contexts
// Three separate contexts
<StateContext.Provider value={count}> {/* Changes often */}
<ActionsContext.Provider value={actions}> {/* Never changes */}
<MetaContext.Provider value={meta}> {/* Never changes */}
{children}
</MetaContext.Provider>
</ActionsContext.Provider>
</StateContext.Provider>
// Now components subscribe to specific contexts!
const { reset } = useCounterActions() // Only ActionsContext
// → NEVER re-renders because ActionsContext value never changes!With split contexts, actions and meta are memoized once and never change. Components subscribing only to ActionsContext truly never re-render!
Implementation Details
1. Split Context Provider
function CounterProvider({ children, initialCount = 0, min = 0, max = 100 }) {
const [count, setCount] = useState(initialCount)
// Memoize actions - stable reference
const actions = useMemo(() => ({
increment: () => setCount(c => Math.min(c + 1, max)),
decrement: () => setCount(c => Math.max(c - 1, min)),
reset: () => setCount(initialCount)
}), [min, max, initialCount]) // Stable dependencies
// Memoize meta - stable reference
const meta = useMemo(() => ({ min, max }), [min, max])
// Three separate providers
return (
<StateContext.Provider value={count}>
<ActionsContext.Provider value={actions}>
<MetaContext.Provider value={meta}>
{children}
</MetaContext.Provider>
</ActionsContext.Provider>
</StateContext.Provider>
)
}2. Selective Subscription
// Display subscribes to state only
function Display() {
const count = useCounterState() // Only StateContext
return <div>{count}</div>
}
// Reset subscribes to actions only
function Reset() {
const { reset } = useCounterActions() // Only ActionsContext
return <button onClick={reset}>Reset</button>
// This component NEVER re-renders when count changes!
}3. Tradeoff: When to Use
Single Context (Simpler): Good for most cases. React Compiler makes re-renders cheap anyway.
Split Contexts (Optimized): Use when you have expensive components that only need actions/meta and shouldn't re-render on state changes.
Key Takeaways
🎯 Single context limitation: When the context value is an object, ALL consumers re-render when ANY part changes, even if they only use stable parts.
🎯 Split contexts solution: Separate contexts for state/actions/meta allow true selective subscriptions.
🎯 Tripartite structure enables this: Organizing as state/actions/meta makes it obvious how to split contexts.
🎯 When to split: Most apps are fine with single context. Split when you have expensive components that shouldn't re-render.
🎯 React Compiler still helps: Even with single context, compiler makes re-renders cheaper by memoizing component output.