perf(frontend): use startTransition for selection updates to keep UI responsive on large sets

This commit is contained in:
Geert Rademakes 2025-08-13 16:03:26 +02:00
parent 1d290bdfa6
commit 017ba31d83

View File

@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect, useCallback, useMemo, memo } from 'react';
import React, { useState, useRef, useEffect, useCallback, useMemo, memo, startTransition } from 'react';
import {
Box,
Flex,
@ -224,14 +224,16 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
// const allPlaylists = useMemo(() => getAllPlaylists(playlists), [playlists, getAllPlaylists]);
const toggleSelection = useCallback((songId: string) => {
setSelectedSongs(prev => {
const newSelection = new Set(prev);
if (newSelection.has(songId)) {
newSelection.delete(songId);
} else {
newSelection.add(songId);
}
return newSelection;
startTransition(() => {
setSelectedSongs(prev => {
const newSelection = new Set(prev);
if (newSelection.has(songId)) {
newSelection.delete(songId);
} else {
newSelection.add(songId);
}
return newSelection;
});
});
}, []);
@ -239,14 +241,16 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
const toggleSelectionRange = useCallback((fromIndex: number, toIndex: number, checked: boolean) => {
if (fromIndex === null || toIndex === null) return;
const [start, end] = fromIndex < toIndex ? [fromIndex, toIndex] : [toIndex, fromIndex];
setSelectedSongs(prev => {
const next = new Set(prev);
for (let i = start; i <= end; i++) {
const id = songs[i]?.id;
if (!id) continue;
if (checked) next.add(id); else next.delete(id);
}
return next;
startTransition(() => {
setSelectedSongs(prev => {
const next = new Set(prev);
for (let i = start; i <= end; i++) {
const id = songs[i]?.id;
if (!id) continue;
if (checked) next.add(id); else next.delete(id);
}
return next;
});
});
}, [songs]);
@ -262,19 +266,18 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
}, [songs, toggleSelection, toggleSelectionRange]);
const toggleSelectAll = useCallback(() => {
setSelectedSongs(prev => {
const noneSelected = prev.size === 0;
const allSelected = prev.size === songs.length && songs.length > 0;
if (noneSelected) {
// Select all from empty state
startTransition(() => {
setSelectedSongs(prev => {
const noneSelected = prev.size === 0;
const allSelected = prev.size === songs.length && songs.length > 0;
if (noneSelected) {
return new Set(songs.map(s => s.id));
}
if (allSelected) {
return new Set();
}
return new Set(songs.map(s => s.id));
}
if (allSelected) {
// Deselect all when everything is selected
return new Set();
}
// Mixed/some selected: clear first, then select all (single state update reflects final state)
return new Set(songs.map(s => s.id));
});
});
}, [songs]);