---
phase: 32-state-update-optimization
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
  - jam-ui/src/hooks/useMixerHelper.js
autonomous: true

must_haves:
  truths:
    - "Mixer categorization doesn't dispatch when content unchanged"
    - "Redux state only updates when mixer arrays meaningfully change"
    - "Unchanged mixer list doesn't trigger downstream re-renders"
  artifacts:
    - path: "jam-ui/src/hooks/useMixerHelper.js"
      provides: "Content comparison before dispatch"
      contains: "mixerArraysEqual"
  key_links:
    - from: "jam-ui/src/hooks/useMixerHelper.js"
      to: "setMetronomeTrackMixers"
      via: "conditional dispatch"
      pattern: "if.*!mixerArraysEqual.*dispatch"
---

<objective>
Prevent redundant Redux dispatches in mixer categorization

Purpose: Stop dispatching mixer category arrays when their content hasn't changed (STATE-03). Currently, every mixer state change triggers 5 category dispatches even if the categories haven't changed, causing unnecessary re-renders.

Output: useMixerHelper with content comparison before each category dispatch
</objective>

<execution_context>
@/Users/nuwan/.claude/get-shit-done/workflows/execute-plan.md
@/Users/nuwan/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/32-state-update-optimization/32-RESEARCH.md

# Source file to modify
@jam-ui/src/hooks/useMixerHelper.js
@jam-ui/src/store/features/mixersSlice.js
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add mixer array comparison helper</name>
  <files>jam-ui/src/hooks/useMixerHelper.js</files>
  <action>
Add a helper function to compare mixer arrays by their IDs. Place this near the top of the file, after imports but before the hook function.

```javascript
/**
 * Compare two mixer arrays by their IDs to determine if content changed.
 * Used to prevent unnecessary Redux dispatches when categorizing mixers.
 *
 * @param {Array} prev - Previous mixer array
 * @param {Array} next - New mixer array
 * @returns {boolean} True if arrays have same content (by ID)
 */
const mixerArraysEqual = (prev, next) => {
  // Same reference = definitely equal
  if (prev === next) return true;

  // Different lengths = definitely not equal
  if (!prev || !next || prev.length !== next.length) return false;

  // Compare by mixer pair IDs (master.id + personal.id creates unique pair key)
  const getIds = (arr) => arr
    .map(pair => `${pair.master?.id || ''}-${pair.personal?.id || ''}`)
    .sort()
    .join(',');

  return getIds(prev) === getIds(next);
};
```

Why ID-based comparison:
- Mixer objects have stable `id` fields
- Deep comparison with JSON.stringify is slow and fragile
- We only care if the set of mixers changed, not individual properties
- Master/personal pair creates unique identifier
  </action>
  <verify>
Helper function exists:
`grep -A 10 "mixerArraysEqual" jam-ui/src/hooks/useMixerHelper.js | head -15`
  </verify>
  <done>
mixerArraysEqual helper function added to useMixerHelper.js
  </done>
</task>

<task type="auto">
  <name>Task 2: Add selectors for current category state</name>
  <files>jam-ui/src/hooks/useMixerHelper.js</files>
  <action>
Import the category selectors and add refs to track previous values. Find the existing selector imports and add the category selectors.

1. Update imports from mixersSlice (around line 10-30):
```javascript
import {
  // ... existing imports ...
  selectMetronomeTrackMixers,
  selectBackingTrackMixers,
  selectJamTrackMixers,
  selectRecordingTrackMixers,
  selectAdhocTrackMixers,
} from '../store/features/mixersSlice';
```

2. Inside the useMixerHelper function, add useRef to track previous values. Place this near other refs/selectors:
```javascript
// Track previous category values for comparison
const prevCategoriesRef = useRef({
  metronome: [],
  backing: [],
  jam: [],
  recording: [],
  adhoc: []
});
```

Note: We use a ref instead of selectors to avoid circular updates. The ref stores the last dispatched values so we can compare before dispatching again.
  </action>
  <verify>
Category selectors imported:
`grep "selectMetronomeTrackMixers\|selectBackingTrackMixers" jam-ui/src/hooks/useMixerHelper.js | head -5`

prevCategoriesRef defined:
`grep "prevCategoriesRef" jam-ui/src/hooks/useMixerHelper.js | head -3`
  </verify>
  <done>
Category selectors imported and prevCategoriesRef added
  </done>
</task>

<task type="auto">
  <name>Task 3: Add conditional dispatch to categorization useEffect</name>
  <files>jam-ui/src/hooks/useMixerHelper.js</files>
  <action>
Modify the categorization useEffect (around lines 236-297) to only dispatch when content changes.

Find the current dispatch block:
```javascript
// Dispatch to Redux
dispatch(setMetronomeTrackMixers(metronomeTrackMixers));
dispatch(setBackingTrackMixers(backingTrackMixers));
dispatch(setJamTrackMixers(jamTrackMixers));
dispatch(setRecordingTrackMixers(recordingTrackMixers));
dispatch(setAdhocTrackMixers(adhocTrackMixers));
```

Replace with conditional dispatches:
```javascript
// Dispatch to Redux ONLY if content changed
// This prevents unnecessary re-renders when mixer objects change but categories stay same

if (!mixerArraysEqual(prevCategoriesRef.current.metronome, metronomeTrackMixers)) {
  console.log('[useMixerHelper] Metronome mixers changed, dispatching');
  dispatch(setMetronomeTrackMixers(metronomeTrackMixers));
  prevCategoriesRef.current.metronome = metronomeTrackMixers;
}

if (!mixerArraysEqual(prevCategoriesRef.current.backing, backingTrackMixers)) {
  console.log('[useMixerHelper] Backing track mixers changed, dispatching');
  dispatch(setBackingTrackMixers(backingTrackMixers));
  prevCategoriesRef.current.backing = backingTrackMixers;
}

if (!mixerArraysEqual(prevCategoriesRef.current.jam, jamTrackMixers)) {
  console.log('[useMixerHelper] Jam track mixers changed, dispatching');
  dispatch(setJamTrackMixers(jamTrackMixers));
  prevCategoriesRef.current.jam = jamTrackMixers;
}

if (!mixerArraysEqual(prevCategoriesRef.current.recording, recordingTrackMixers)) {
  console.log('[useMixerHelper] Recording mixers changed, dispatching');
  dispatch(setRecordingTrackMixers(recordingTrackMixers));
  prevCategoriesRef.current.recording = recordingTrackMixers;
}

if (!mixerArraysEqual(prevCategoriesRef.current.adhoc, adhocTrackMixers)) {
  console.log('[useMixerHelper] Adhoc mixers changed, dispatching');
  dispatch(setAdhocTrackMixers(adhocTrackMixers));
  prevCategoriesRef.current.adhoc = adhocTrackMixers;
}
```

Note: Console logs are useful for debugging but can be commented out in production. They help verify that dispatches are being skipped when expected.
  </action>
  <verify>
Conditional dispatch pattern present:
`grep -A 3 "mixerArraysEqual.*metronome" jam-ui/src/hooks/useMixerHelper.js | head -5`

All 5 categories have conditional checks:
`grep -c "mixerArraysEqual.*prevCategoriesRef" jam-ui/src/hooks/useMixerHelper.js` should be 5

ESLint passes:
`cd jam-ui && npx eslint src/hooks/useMixerHelper.js --max-warnings=0`
  </verify>
  <done>
Mixer categorization only dispatches when content changes, tracked via prevCategoriesRef
  </done>
</task>

</tasks>

<verification>
1. Helper function added:
   ```bash
   grep -c "mixerArraysEqual" jam-ui/src/hooks/useMixerHelper.js
   ```
   Should return at least 6 (1 definition + 5 usages)

2. Conditional dispatch pattern:
   ```bash
   grep "if (!mixerArraysEqual" jam-ui/src/hooks/useMixerHelper.js | wc -l
   ```
   Should return 5

3. ESLint clean:
   ```bash
   cd jam-ui && npx eslint src/hooks/useMixerHelper.js --max-warnings=0
   ```
</verification>

<success_criteria>
- [ ] mixerArraysEqual helper function created
- [ ] prevCategoriesRef tracks previously dispatched values
- [ ] All 5 category dispatches use conditional check
- [ ] prevCategoriesRef updated only when dispatch occurs
- [ ] ESLint passes with no warnings
</success_criteria>

<output>
After completion, create `.planning/phases/32-state-update-optimization/32-02-SUMMARY.md`
</output>
