Optimize PaginatedSongList performance to prevent re-renders
- Memoized drag handlers to prevent inline function recreation - Added custom React.memo comparison for SongItem components - Optimized hasMusicFile calculation with useMemo - Removed debug console.log statements - Fixed inline function creation in song mapping This should significantly reduce re-renders and improve song selection performance.
This commit is contained in:
parent
4c63228619
commit
383f3476f0
@ -70,10 +70,14 @@ const SongItem = memo<{
|
||||
// Memoize the formatted duration to prevent recalculation
|
||||
const formattedDuration = useMemo(() => formatDuration(song.totalTime || ''), [song.totalTime]);
|
||||
const handleClick = useCallback(() => {
|
||||
console.log('SongItem clicked:', song.title);
|
||||
onSelect(song);
|
||||
}, [onSelect, song]);
|
||||
|
||||
const hasMusicFile = useMemo(() =>
|
||||
song.s3File?.hasS3File || song.hasMusicFile || false,
|
||||
[song.s3File?.hasS3File, song.hasMusicFile]
|
||||
);
|
||||
|
||||
const handleCheckboxClick = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation();
|
||||
if (onCheckboxToggle) {
|
||||
@ -85,14 +89,10 @@ const SongItem = memo<{
|
||||
|
||||
const handlePlayClick = useCallback((e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (onPlaySong && (song.s3File?.hasS3File || song.hasMusicFile)) {
|
||||
if (onPlaySong && hasMusicFile) {
|
||||
onPlaySong(song);
|
||||
}
|
||||
}, [onPlaySong, song]);
|
||||
|
||||
const hasMusicFile = (song: Song): boolean => {
|
||||
return song.s3File?.hasS3File || song.hasMusicFile || false;
|
||||
};
|
||||
}, [onPlaySong, hasMusicFile, song]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@ -138,7 +138,7 @@ const SongItem = memo<{
|
||||
{song.averageBpm} BPM
|
||||
</Text>
|
||||
</Box>
|
||||
{hasMusicFile(song) && onPlaySong && (
|
||||
{hasMusicFile && onPlaySong && (
|
||||
<IconButton
|
||||
aria-label="Play song"
|
||||
icon={<FiPlay />}
|
||||
@ -156,6 +156,28 @@ const SongItem = memo<{
|
||||
|
||||
SongItem.displayName = 'SongItem';
|
||||
|
||||
// Custom comparison function to prevent unnecessary re-renders
|
||||
const areEqual = (prevProps: any, nextProps: any) => {
|
||||
return (
|
||||
prevProps.song.id === nextProps.song.id &&
|
||||
prevProps.isSelected === nextProps.isSelected &&
|
||||
prevProps.isHighlighted === nextProps.isHighlighted &&
|
||||
prevProps.showCheckbox === nextProps.showCheckbox &&
|
||||
prevProps.showDropIndicatorTop === nextProps.showDropIndicatorTop &&
|
||||
prevProps.index === nextProps.index &&
|
||||
prevProps.song.title === nextProps.song.title &&
|
||||
prevProps.song.artist === nextProps.song.artist &&
|
||||
prevProps.song.totalTime === nextProps.song.totalTime &&
|
||||
prevProps.song.tonality === nextProps.song.tonality &&
|
||||
prevProps.song.averageBpm === nextProps.song.averageBpm &&
|
||||
prevProps.song.s3File?.hasS3File === nextProps.song.s3File?.hasS3File &&
|
||||
prevProps.song.hasMusicFile === nextProps.song.hasMusicFile
|
||||
);
|
||||
};
|
||||
|
||||
// Apply custom comparison to SongItem
|
||||
const OptimizedSongItem = memo(SongItem, areEqual);
|
||||
|
||||
export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
|
||||
songs,
|
||||
onAddToPlaylist,
|
||||
@ -303,6 +325,54 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
|
||||
onSongSelect(song);
|
||||
}, [onSongSelect]);
|
||||
|
||||
// Memoized drag handlers to prevent re-renders
|
||||
const createRowDragOver = useCallback((index: number) => {
|
||||
if (!onReorder || !currentPlaylist) return undefined;
|
||||
return (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
setDragHoverIndex(index);
|
||||
};
|
||||
}, [onReorder, currentPlaylist]);
|
||||
|
||||
const createRowDrop = useCallback((index: number) => {
|
||||
if (!onReorder || !currentPlaylist) return undefined;
|
||||
return async (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
const fromId = e.dataTransfer.getData('text/song-id');
|
||||
const multiJson = e.dataTransfer.getData('application/json');
|
||||
let multiIds: string[] | null = null;
|
||||
if (multiJson) {
|
||||
try {
|
||||
const parsed = JSON.parse(multiJson);
|
||||
if (parsed && parsed.type === 'songs' && Array.isArray(parsed.songIds)) {
|
||||
multiIds = parsed.songIds as string[];
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
if (!fromId && !multiIds) return;
|
||||
const toId = songs[index]?.id;
|
||||
if (!toId) return;
|
||||
if (multiIds && multiIds.length > 0) {
|
||||
await api.moveTracksInPlaylist(currentPlaylist, multiIds, toId);
|
||||
} else {
|
||||
await api.moveTrackInPlaylist(currentPlaylist, fromId!, toId);
|
||||
}
|
||||
await onReorder(songs.map(s => s.id));
|
||||
setDragHoverIndex(null);
|
||||
setIsReorderDragging(false);
|
||||
};
|
||||
}, [onReorder, currentPlaylist, songs]);
|
||||
|
||||
const createRowDragStartCapture = useCallback((song: Song) => {
|
||||
if (!currentPlaylist) return undefined;
|
||||
return (e: React.DragEvent) => {
|
||||
e.dataTransfer.setData('text/song-id', song.id);
|
||||
try { e.dataTransfer.effectAllowed = 'move'; } catch {}
|
||||
try { e.dataTransfer.dropEffect = 'move'; } catch {}
|
||||
setIsReorderDragging(true);
|
||||
};
|
||||
}, [currentPlaylist]);
|
||||
|
||||
// Memoized search handler with debouncing
|
||||
// Search handled inline via localSearchQuery effect
|
||||
|
||||
@ -591,7 +661,7 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
|
||||
{songs.map((song, index) => {
|
||||
const allowReorder = Boolean(onReorder && currentPlaylist);
|
||||
return (
|
||||
<SongItem
|
||||
<OptimizedSongItem
|
||||
key={song.id}
|
||||
song={song}
|
||||
isSelected={selectedSongs.has(song.id)}
|
||||
@ -604,44 +674,9 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
|
||||
onPlaySong={onPlaySong}
|
||||
showDropIndicatorTop={dragHoverIndex === index}
|
||||
onDragStart={handleDragStart}
|
||||
onRowDragOver={allowReorder ? ((e: React.DragEvent) => {
|
||||
if (!onReorder || !currentPlaylist) return;
|
||||
e.preventDefault();
|
||||
setDragHoverIndex(index);
|
||||
}) : undefined}
|
||||
onRowDrop={allowReorder ? (async (e: React.DragEvent) => {
|
||||
if (!onReorder || !currentPlaylist) return;
|
||||
e.preventDefault();
|
||||
const fromId = e.dataTransfer.getData('text/song-id');
|
||||
const multiJson = e.dataTransfer.getData('application/json');
|
||||
let multiIds: string[] | null = null;
|
||||
if (multiJson) {
|
||||
try {
|
||||
const parsed = JSON.parse(multiJson);
|
||||
if (parsed && parsed.type === 'songs' && Array.isArray(parsed.songIds)) {
|
||||
multiIds = parsed.songIds as string[];
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
if (!fromId && !multiIds) return;
|
||||
const toId = songs[index]?.id;
|
||||
if (!toId) return;
|
||||
if (multiIds && multiIds.length > 0) {
|
||||
await api.moveTracksInPlaylist(currentPlaylist, multiIds, toId);
|
||||
} else {
|
||||
await api.moveTrackInPlaylist(currentPlaylist, fromId!, toId);
|
||||
}
|
||||
await onReorder(songs.map(s => s.id));
|
||||
setDragHoverIndex(null);
|
||||
setIsReorderDragging(false);
|
||||
}) : undefined}
|
||||
onRowDragStartCapture={allowReorder ? ((e: React.DragEvent) => {
|
||||
if (!currentPlaylist) return;
|
||||
e.dataTransfer.setData('text/song-id', song.id);
|
||||
try { e.dataTransfer.effectAllowed = 'move'; } catch {}
|
||||
try { e.dataTransfer.dropEffect = 'move'; } catch {}
|
||||
setIsReorderDragging(true);
|
||||
}) : undefined}
|
||||
onRowDragOver={allowReorder ? createRowDragOver(index) : undefined}
|
||||
onRowDrop={allowReorder ? createRowDrop(index) : undefined}
|
||||
onRowDragStartCapture={allowReorder ? createRowDragStartCapture(song) : undefined}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user