refactor: add React.memo to performance-critical components#1391
Conversation
There was a problem hiding this comment.
Pull request overview
This PR aims to reduce unnecessary React re-renders in performance-sensitive UI areas by wrapping frequently-rendered components with React.memo, improving responsiveness in the session grid and mixer.
Changes:
- Memoized
SessionSceneStripto reduce scene row re-renders. - Memoized
SessionClipSlotViewto reduce slot re-renders. - Memoized the mixer
ChannelStripto reduce re-renders across tracks.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/components/session/SessionSceneStrip.tsx | Wrapes SessionSceneStrip with React.memo. |
| src/components/session/SessionClipSlotView.tsx | Wrapes SessionClipSlotView with React.memo. |
| src/components/mixer/MixerPanel.tsx | Wrapes ChannelStrip with React.memo inside the mixer panel. |
Comments suppressed due to low confidence (1)
src/components/session/SessionSceneStrip.tsx:13
React.memowon’t prevent re-renders triggered by Zustand subscriptions. This component subscribes to the fullslotsarray (useSessionStore((s) => s.slots)), so any slot update (even in other scenes) will re-render everySessionSceneStrip, largely defeating the intended per-scene memoization. Consider selecting only the slots forscene.indexfrom the store (ideally via a pre-indexed structure or a selector + equality fn such asshallow) so unrelated slot changes don’t invalidate this component.
export const SessionSceneStrip = React.memo(function SessionSceneStrip({ scene }: SessionSceneStripProps) {
const launchScene = useSessionStore((s) => s.launchScene);
const stopScene = useSessionStore((s) => s.stopScene);
const renameScene = useSessionStore((s) => s.renameScene);
const slots = useSessionStore((s) => s.slots);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const ChannelStrip = React.memo(function ChannelStrip({ track, faderHeight, returnTracks }: ChannelStripProps) { | ||
| const updateTrack = useProjectStore((s) => s.updateTrack); |
There was a problem hiding this comment.
Memoization here depends on prop identity. returnTracks is passed as an array prop, and in MixerPanel it’s computed via project.returnTracks ?? [] (which creates a new empty array when undefined). That can cause ChannelStrip to re-render even when track is unchanged, reducing the benefit of React.memo. Consider stabilizing the returnTracks reference (e.g., reuse a module-level empty array / memoize) or avoid passing an array that frequently changes identity.
| } | ||
|
|
||
| function ChannelStrip({ track, faderHeight, returnTracks }: ChannelStripProps) { | ||
| const ChannelStrip = React.memo(function ChannelStrip({ track, faderHeight, returnTracks }: ChannelStripProps) { |
There was a problem hiding this comment.
This PR closes #1365, but the issue’s acceptance criteria also call out additional work (e.g., splitting/memoizing EffectCard and memoizing SessionView). If those are intentionally out of scope for this PR, it would be better to avoid closing the issue (or update the issue/PR description to reflect what remains) so tracking isn’t lost.
Summary
Wraps frequently-rendered components with React.memo:
ClipBlock and TrackLane were already memoized (verified in codebase).
Test plan
npx tsc --noEmit— 0 errorsnpm test— 3865 passedCloses #1365
🤖 Generated with Claude Code