# Phase 29: Context Optimization - Research

**Researched:** 2026-03-05
**Domain:** React context performance optimization
**Confidence:** HIGH

## Summary

Phase 29 addresses unnecessary re-renders caused by unmemoized context provider values in MixersContext and the coupling of high-frequency VU updates with low-frequency mixer configuration updates. Phase 28 successfully extracted VU data into an external store, but MixersContext still creates a new value object on every render (line 10 of MixersContext.js), triggering cascading re-renders in all consumer components even when the underlying data hasn't changed.

The solution involves three complementary strategies:
1. **Memoize context provider values** - Use `useMemo` to prevent new object creation on every render
2. **Split contexts by update frequency** - Separate VuContext (already simplified in Phase 28) from MixersContext configuration
3. **Optimize context consumers** - Use `React.memo` and selector patterns to minimize re-render scope

This phase builds directly on Phase 28's work. VuContext was already simplified to provide vuStore reference; now MixersContext needs similar optimization for mixer configuration data (volume, pan, mute state, etc.).

**Primary recommendation:** Wrap the MixersContext.Provider value with useMemo, using appropriate dependencies from useMixerHelper. Since Phase 28 already separated VU updates via external store, this phase focuses exclusively on preventing re-renders from mixer configuration changes.

## Standard Stack

The established libraries/tools for this domain:

### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| React useMemo | built-in (16.13.1) | Memoize context values | Official React API for preventing object recreation |
| React useCallback | built-in (16.13.1) | Memoize function references | Stabilizes functions passed to context consumers |
| React memo | built-in (16.13.1) | Prevent component re-renders | Standard pattern for optimizing context consumers |

### Supporting
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| use-sync-external-store | 1.6.0 (already installed) | External store subscription | Already used for vuStore in Phase 28 |
| @reduxjs/toolkit | 1.6.1 (already installed) | State management | Already used for mixer state in Redux |

### Alternatives Considered
| Instead of | Could Use | Tradeoff |
|------------|-----------|----------|
| useMemo | use-context-selector | Added dependency; React 19+ has native support; useMemo simpler for this phase |
| Context splitting | Single optimized context | Splitting better separates concerns; VuContext already exists |
| React.memo | PureComponent | Functional components are codebase standard |

**Installation:**
```bash
# No new dependencies required
# All optimization APIs are React built-ins or already installed
```

## Architecture Patterns

### Recommended Project Structure
```
src/
├── context/
│   ├── MixersContext.js        # MODIFY: Add useMemo to provider value
│   ├── VuContext.js             # ALREADY OPTIMIZED in Phase 28
│   └── GlobalContext.js         # CHECK: May need useMemo for trackVolumeObject
├── hooks/
│   ├── useMixerHelper.js        # VERIFY: Return stable references
│   └── useVuHelpers.js          # ALREADY OPTIMIZED in Phase 28
└── components/
    └── client/
        ├── JKSessionMyTrack.js       # CONSUMER: Verify memo-wrapped
        ├── SessionTrackGain.js       # CONSUMER: Verify memo-wrapped
        └── JKSessionVolumeModal.js   # CONSUMER: Verify memo-wrapped
```

### Pattern 1: Memoized Context Provider Value
**What:** Wrap the context provider value with useMemo to prevent object recreation on every render.
**When to use:** When context value is an object containing multiple properties or functions.
**Example:**
```javascript
// Source: React official docs + Kent C. Dodds blog
// Modify MixersContext.js

import React, { createContext, useContext, useMemo } from 'react';
import useMixerHelper from '../hooks/useMixerHelper.js';

const MixersContext = createContext();

export const MixersProvider = ({ children }) => {
  const mixerHelper = useMixerHelper();

  // Memoize the context value - only recreate if mixerHelper changes
  const value = useMemo(() => mixerHelper, [mixerHelper]);

  return (
    <MixersContext.Provider value={value}>
      {children}
    </MixersContext.Provider>
  );
};

export const useMixersContext = () => {
  const context = useContext(MixersContext);
  if (!context) {
    throw new Error('useMixersContext must be used within a MixersProvider');
  }
  return context;
};

export default MixersContext;
```

### Pattern 2: Stable Function References with useCallback
**What:** Wrap functions returned from hooks with useCallback to ensure stable references across renders.
**When to use:** When functions are part of context value or passed as props to memoized components.
**Example:**
```javascript
// Source: React official docs for useCallback
// Pattern to apply in useMixerHelper.js

const faderChanged = useCallback(async (data, mixers, gainType, controlGroup) => {
  // Implementation stays the same
  // ...existing code...
}, [getOriginalVolume, getMixer, fillTrackVolumeObject, setMixerVolume, allMixers, trackVolumeObject, dispatch]);

// CRITICAL: Ensure ALL functions in the return object use useCallback
// so that the entire mixerHelper object has stable references
```

### Pattern 3: Context Consumer Memoization
**What:** Wrap context consumer components with React.memo to prevent re-renders when props haven't changed.
**When to use:** For all components that consume context and render frequently.
**Example:**
```javascript
// Source: React official docs for memo
// Pattern for SessionTrackGain.js

import React, { memo } from 'react';
import { useMixersContext } from '../../context/MixersContext';

const SessionTrackGain = memo(function SessionTrackGain({
  mixers,
  gainType,
  controlGroup,
  sessionController,
  orientation = 'vertical'
}) {
  const mixerHelper = useMixersContext();
  // ... component implementation ...
});

export default SessionTrackGain;
```

### Pattern 4: Split Context by Update Frequency
**What:** Separate high-frequency updates (VU) from low-frequency updates (mixer config) into different contexts.
**When to use:** When context contains data that updates at dramatically different rates.
**Example:**
```javascript
// Source: Kent C. Dodds optimization guide + developerway.com patterns
// This pattern is ALREADY IMPLEMENTED in Phase 28

// VuContext.js - High-frequency updates (50-70/sec) via external store
export const VuProvider = ({ children }) => {
  const vuHelpers = useVuHelpers();

  const value = useMemo(() => ({
    ...vuHelpers,
    vuStore,
  }), [vuHelpers]);

  return <VuContext.Provider value={value}>{children}</VuContext.Provider>;
};

// MixersContext.js - Low-frequency updates (user interactions)
export const MixersProvider = ({ children }) => {
  const mixerHelper = useMixerHelper();
  const value = useMemo(() => mixerHelper, [mixerHelper]);
  return <MixersContext.Provider value={value}>{children}</MixersContext.Provider>;
};
```

### Anti-Patterns to Avoid
- **Creating new objects in provider:** `value={{ ...mixerHelper }}` creates new object every render
- **Inline object literals:** `value={{ data, functions }}` always creates new reference
- **Missing dependencies in useMemo:** Stale closures over mixer state
- **Overusing useMemo:** Don't memoize primitive values or simple calculations
- **Forgetting useCallback for functions:** Functions recreated on every render break memo
- **Not combining memo with useMemo:** Memoizing context value alone doesn't prevent consumer re-renders if props change

## Don't Hand-Roll

Problems that look simple but have existing solutions:

| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Context value comparison | Custom shallow equality | useMemo with correct dependencies | React team optimized useMemo for this; handles edge cases |
| Function reference stability | Manual ref tracking | useCallback hook | Built-in, well-tested, works with concurrent rendering |
| Component re-render prevention | Custom shouldComponentUpdate | React.memo | Optimized for functional components, simpler API |
| Context selector pattern | Custom subscription system | Use useMemo at consumer level | React 19+ has useContextSelector; for React 16 useMemo simpler than library |

**Key insight:** React's built-in memoization hooks (useMemo, useCallback, memo) are heavily optimized for concurrent rendering and cover the vast majority of context optimization needs. Adding external libraries like use-context-selector adds complexity and bundle size without significant benefit for this specific use case where context updates are already infrequent (user interactions only).

## Common Pitfalls

### Pitfall 1: useMemo Dependencies Not Comprehensive
**What goes wrong:** Context value still recreated on every render, defeating memoization
**Why it happens:** Missing dependency in useMemo array, or dependency itself recreates every render
**How to avoid:**
- Use ESLint react-hooks/exhaustive-deps rule
- Ensure hook return values are stable (useCallback wraps functions)
- Verify dependencies with React DevTools Profiler
**Warning signs:** React DevTools shows "MixersProvider" re-rendering every time, even without state changes

### Pitfall 2: Memoizing Value But Not Function References
**What goes wrong:** Context value object appears memoized, but functions inside recreate, breaking memo
**Why it happens:** Functions returned from hooks aren't wrapped in useCallback
**How to avoid:** Wrap ALL functions in useCallback with correct dependencies
**Warning signs:** memo-wrapped consumers still re-render; React DevTools highlights function prop changes

### Pitfall 3: memo Without Stable Props
**What goes wrong:** Component wrapped in memo still re-renders frequently
**Why it happens:** Parent passes new object/array/function references as props each render
**How to avoid:**
- useMemo for object/array props
- useCallback for function props
- Pass primitive values when possible
**Warning signs:** Profiler shows "did not skip render" for memo components

### Pitfall 4: Overusing useMemo for Everything
**What goes wrong:** Code becomes harder to read, minimal performance benefit
**Why it happens:** Premature optimization without measuring actual performance impact
**How to avoid:**
- Only memoize context provider values and expensive computations
- Don't memoize primitive values or simple JSX
- Profile with React DevTools before adding memoization
**Warning signs:** Code complexity increases but Profiler shows no improvement

### Pitfall 5: Stale Closures from Missing Dependencies
**What goes wrong:** Memoized functions reference old state/props, causing bugs
**Why it happens:** Dependencies array missing reactive values, creating closure over stale data
**How to avoid:**
- Include ALL reactive values in dependency array
- Use ESLint warnings as guide
- Test edge cases where state updates quickly
**Warning signs:** Functions behave with old data; intermittent bugs during rapid interactions

### Pitfall 6: Context Splitting Without Clear Boundaries
**What goes wrong:** Confusion about which context provides what data
**Why it happens:** Context split arbitrarily without clear domain separation
**How to avoid:**
- Split by update frequency (VU vs config) or domain (auth vs app state)
- Document each context's purpose clearly
- Name contexts descriptively (VuContext, MixersContext)
**Warning signs:** Consumers import multiple contexts; unclear which context owns which data

## Code Examples

Verified patterns from official sources:

### Complete MixersContext Optimization
```javascript
// Source: React official docs + codebase pattern
// Modify jam-ui/src/context/MixersContext.js

import React, { createContext, useContext, useMemo } from 'react';
import useMixerHelper from '../hooks/useMixerHelper.js';

const MixersContext = createContext();

export const MixersProvider = ({ children }) => {
  const mixerHelper = useMixerHelper();

  // CRITICAL: Memoize the entire value object
  // Since useMixerHelper returns stable references (via useCallback),
  // this prevents new object creation on every render
  const value = useMemo(() => mixerHelper, [mixerHelper]);

  return (
    <MixersContext.Provider value={value}>
      {children}
    </MixersContext.Provider>
  );
};

export const useMixersContext = () => {
  const context = useContext(MixersContext);
  if (!context) {
    throw new Error('useMixersContext must be used within a MixersProvider');
  }
  return context;
};

export default MixersContext;
```

### Verify useMixerHelper Returns Stable References
```javascript
// Source: Current useMixerHelper.js pattern + React useCallback docs
// Audit jam-ui/src/hooks/useMixerHelper.js return statement

// BEFORE (if needed):
return {
  faderChanged: (data, mixers) => { /* impl */ }, // ❌ New function every render
  myTracks, // ❌ May recreate if not memoized
  mixMode
};

// AFTER:
const faderChanged = useCallback(async (data, mixers, gainType, controlGroup) => {
  // ...existing implementation...
}, [getOriginalVolume, getMixer, fillTrackVolumeObject, setMixerVolume, allMixers, trackVolumeObject, dispatch]);

const myTracks = useMemo(() => {
  // ...existing computation...
}, [currentSession, isConnected, jamClient, allMixers, mixMode, findMixerForTrack, getParticipant, server.clientId]);

// VERIFIED: All functions use useCallback, computed values use useMemo
return {
  isReady,
  session: currentSession,
  mixers: allMixers,
  myTracks, // ✅ Memoized
  findMixerForTrack, // ✅ useCallback
  updateMixerData, // ✅ useCallback
  updateVU, // ✅ useCallback
  simulatedMusicCategoryMixers,
  simulatedChatCategoryMixers,
  mixMode,
  faderChanged, // ✅ useCallback
  initGain, // ✅ useCallback
  setMixerPan, // ✅ useCallback
  getAudioInputCategoryMixer, // ✅ useCallback
  getChatCategoryMixer, // ✅ useCallback
  backingTracks,
  jamTracks,
  recordedTracks
};
```

### Memoize Context Consumers
```javascript
// Source: React memo documentation
// Pattern for SessionTrackGain.js and other consumers

import React, { memo } from 'react';
import { useMixersContext } from '../../context/MixersContext';

// Wrap the entire component export with memo
const SessionTrackGain = memo(function SessionTrackGain({
  mixers,
  gainType,
  controlGroup,
  sessionController,
  orientation = 'vertical'
}) {
  const mixerHelper = useMixersContext();
  const faderHelpers = useFaderHelpers();

  // Component implementation stays the same
  // ...

  return (/* JSX */);
});

// Add display name for debugging
SessionTrackGain.displayName = 'SessionTrackGain';

export default SessionTrackGain;
```

### Verify GlobalContext Memoization
```javascript
// Source: Current GlobalContext.js + memoization pattern
// Check jam-ui/src/context/GlobalContext.js

export const GlobalProvider = ({ children }) => {
  const [trackVolumeObject, setTrackVolumeObject] = useState({ /* ... */ });
  const [globalObject, setGlobalObject] = useState({ /* ... */ });
  const [videoEnabled, setVideoEnabled] = useState(false);

  const { metronomeState, updateMetronomeState, openMetronome, closeMetronome, resetMetronome } = useMetronomeState();

  // CRITICAL: Memoize the provider value
  const value = useMemo(() => ({
    trackVolumeObject,
    setTrackVolumeObject,
    globalObject,
    setGlobalObject,
    metronomeState,
    updateMetronomeState,
    openMetronome,
    closeMetronome,
    resetMetronome,
  }), [trackVolumeObject, globalObject, metronomeState, updateMetronomeState, openMetronome, closeMetronome, resetMetronome]);

  return (
    <GlobalContext.Provider value={value}>
      {children}
    </GlobalContext.Provider>
  );
};
```

### Testing Context Optimization
```javascript
// Source: React DevTools Profiler best practices
// Pattern for verification after implementing optimization

// Use React DevTools Profiler to verify:
// 1. Record a session with Profiler
// 2. Move a volume slider (triggers MixersContext update)
// 3. Check which components re-rendered

// BEFORE optimization:
// - MixersProvider re-renders ❌
// - ALL consumers re-render (JKSessionMyTrack, SessionTrackGain, etc.) ❌
// - VU meters re-render ❌

// AFTER optimization:
// - MixersProvider does NOT re-render ✅
// - Only consumers with changed props re-render ✅
// - VU meters do NOT re-render (external store) ✅

// Success criteria from requirements:
// - CTX-01: MixersContext.Provider value is memoized ✅
// - CTX-02: VuContext separated from MixersContext ✅ (Phase 28)
// - CTX-03: Volume slider change doesn't re-render VU meters ✅
// - Success: VU update doesn't re-render volume sliders ✅
```

## State of the Art

| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| Inline context value object | useMemo-wrapped value | React 16.8 (2019) | Prevents object recreation on every render |
| Class components with shouldComponentUpdate | React.memo for functional components | React 16.6 (2018) | Simpler API, better with hooks |
| Single context for all data | Context splitting by domain/frequency | Best practice (2020+) | Reduces re-render scope |
| Props drilling | Context API | React 16.3 (2018) | Cleaner component tree, but needs optimization |
| use-context-selector library | Native useContextSelector | React 19 (2024) | Built-in selector pattern, but not yet available in React 16 |

**Deprecated/outdated:**
- **PureComponent for context consumers:** Functional components with memo are now standard
- **Context.Consumer render prop:** useContext hook is cleaner, more readable
- **Separate state/dispatch contexts for every context:** Only split when update frequency differs significantly

## Open Questions

Things that couldn't be fully resolved:

1. **Exact dependencies for MixersContext useMemo**
   - What we know: Value should be memoized, dependencies should include mixerHelper
   - What's unclear: Whether useMixerHelper return value is already stable (needs code audit)
   - Recommendation: Audit useMixerHelper to ensure all functions use useCallback; if stable, dependency is just [mixerHelper]

2. **Impact of memoizing GlobalContext**
   - What we know: GlobalContext also provides trackVolumeObject which updates frequently
   - What's unclear: Whether GlobalContext needs same optimization treatment
   - Recommendation: Check if GlobalContext consumers experience performance issues; if yes, apply same pattern

3. **Optimal granularity for context splitting**
   - What we know: VuContext already separated (Phase 28); MixersContext contains config
   - What's unclear: Whether MixersContext should be further split (e.g., MixerConfigContext vs MixerActionsContext)
   - Recommendation: Start with single memoized MixersContext; split further only if profiling shows issues

4. **React DevTools Profiler verification methodology**
   - What we know: Need to verify no re-renders from context updates
   - What's unclear: Exact profiler setup and metrics to capture
   - Recommendation: Document profiler verification steps in verification task; include before/after screenshots

## Sources

### Primary (HIGH confidence)
- [React useMemo official docs](https://react.dev/reference/react/useMemo) - API, dependencies, best practices
- [React useCallback official docs](https://react.dev/reference/react/useCallback) - Stabilizing function references
- [React memo official docs](https://react.dev/reference/react/memo) - Component memoization, context caveats
- [Kent C. Dodds - How to optimize your context value](https://kentcdodds.com/blog/how-to-optimize-your-context-value) - Authoritative pattern guide

### Secondary (MEDIUM confidence)
- [How to Handle React Context Performance Issues (2026)](https://oneuptime.com/blog/post/2026-01-24-react-context-performance-issues/view) - Modern context optimization patterns
- [developerway.com - React re-renders guide](https://www.developerway.com/posts/react-re-renders-guide) - Comprehensive re-render patterns
- [developerway.com - Performant React apps with Context](https://www.developerway.com/posts/how-to-write-performant-react-apps-with-context) - Context performance strategies
- [Josh Comeau - useMemo and useCallback](https://www.joshwcomeau.com/react/usememo-and-usecallback/) - Clear explanations with examples

### Tertiary (LOW confidence)
- [use-context-selector npm](https://www.npmjs.com/package/use-context-selector) - Selector pattern library (React 19+ has native support)
- WebSearch results for React context optimization 2026 - General patterns confirmed

## Metadata

**Confidence breakdown:**
- Standard stack: HIGH - All optimization APIs are React built-ins, well-documented
- Architecture: HIGH - Patterns verified against React official docs and Kent C. Dodds authoritative sources
- Pitfalls: HIGH - Common issues documented in official React docs and multiple authoritative sources

**Research date:** 2026-03-05
**Valid until:** 2026-04-05 (30 days - stable React patterns, no breaking changes expected)

**Key constraints:**
- React 16.13.1 (no React 19 useContextSelector available)
- VuContext already optimized in Phase 28 with external store
- MixersContext identified as root cause (line 10 creates new object every render)
- Redux already used for state management (no architectural change needed)
