← Home

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)

1
5
1

• 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)

1
5
1

• 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-render

The 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.