7.9 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 32-state-update-optimization | 02 | execute | 1 |
|
true |
|
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
<execution_context> @/Users/nuwan/.claude/get-shit-done/workflows/execute-plan.md @/Users/nuwan/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/32-state-update-optimization/32-RESEARCH.mdSource file to modify
@jam-ui/src/hooks/useMixerHelper.js @jam-ui/src/store/features/mixersSlice.js
Task 1: Add mixer array comparison helper jam-ui/src/hooks/useMixerHelper.js 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./**
* 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
idfields - 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
Helper function exists:
grep -A 10 "mixerArraysEqual" jam-ui/src/hooks/useMixerHelper.js | head -15mixerArraysEqual helper function added to useMixerHelper.js
- Update imports from mixersSlice (around line 10-30):
import {
// ... existing imports ...
selectMetronomeTrackMixers,
selectBackingTrackMixers,
selectJamTrackMixers,
selectRecordingTrackMixers,
selectAdhocTrackMixers,
} from '../store/features/mixersSlice';
- Inside the useMixerHelper function, add useRef to track previous values. Place this near other refs/selectors:
// 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.
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
Category selectors imported and prevCategoriesRef added
Find the current dispatch block:
// Dispatch to Redux
dispatch(setMetronomeTrackMixers(metronomeTrackMixers));
dispatch(setBackingTrackMixers(backingTrackMixers));
dispatch(setJamTrackMixers(jamTrackMixers));
dispatch(setRecordingTrackMixers(recordingTrackMixers));
dispatch(setAdhocTrackMixers(adhocTrackMixers));
Replace with conditional dispatches:
// 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.
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
Mixer categorization only dispatches when content changes, tracked via prevCategoriesRef
-
Conditional dispatch pattern:
grep "if (!mixerArraysEqual" jam-ui/src/hooks/useMixerHelper.js | wc -lShould return 5
-
ESLint clean:
cd jam-ui && npx eslint src/hooks/useMixerHelper.js --max-warnings=0
<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>