Fix O(n²) lookup, memoize components and callbacks in QueueSection
- Build a participantMap (Map<id, participant>) with useMemo so each queue item lookup is O(1) instead of O(n) per render - Memoize queueIds array for SortableContext to avoid churn - Wrap handleDragEnd in useCallback so DndContext gets a stable reference - Wrap SortableQueueItem in memo so it skips re-renders when props haven't changed (important since the parent re-renders every second during a live draft from timer-update socket events) - Pass onRemoveFromQueue and onMakePick directly as props instead of creating new arrow functions per item per render; SortableQueueItem now calls them with the relevant id itself https://claude.ai/code/session_01HPtNkL5m9xhYgtzWaGViSC
This commit is contained in:
parent
21119abf12
commit
c499fb9595
1 changed files with 29 additions and 24 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { memo } from "react";
|
||||
import { memo, useCallback, useMemo } from "react";
|
||||
import { GripVertical } from "lucide-react";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
|
|
@ -47,7 +47,7 @@ interface QueueSectionProps {
|
|||
}
|
||||
|
||||
// Sortable queue item component
|
||||
function SortableQueueItem({
|
||||
const SortableQueueItem = memo(function SortableQueueItem({
|
||||
item,
|
||||
index,
|
||||
participantName,
|
||||
|
|
@ -61,8 +61,8 @@ function SortableQueueItem({
|
|||
participantName: string;
|
||||
sportName?: string;
|
||||
canPick: boolean;
|
||||
onRemove: () => void;
|
||||
onDraft?: () => void;
|
||||
onRemove: (queueId: string) => void;
|
||||
onDraft?: (participantId: string) => void;
|
||||
}) {
|
||||
const {
|
||||
attributes,
|
||||
|
|
@ -109,7 +109,7 @@ function SortableQueueItem({
|
|||
variant="default"
|
||||
size="sm"
|
||||
className="h-7 text-xs bg-electric text-background hover:bg-electric/90"
|
||||
onClick={onDraft}
|
||||
onClick={() => onDraft(item.participantId)}
|
||||
>
|
||||
Draft
|
||||
</Button>
|
||||
|
|
@ -118,7 +118,7 @@ function SortableQueueItem({
|
|||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||
onClick={onRemove}
|
||||
onClick={() => onRemove(item.id)}
|
||||
title="Remove from queue"
|
||||
>
|
||||
<span className="text-lg">×</span>
|
||||
|
|
@ -126,7 +126,7 @@ function SortableQueueItem({
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const QueueSection = memo(function QueueSection({
|
||||
queue,
|
||||
|
|
@ -150,18 +150,25 @@ export const QueueSection = memo(function QueueSection({
|
|||
})
|
||||
);
|
||||
|
||||
const handleDragEnd = (event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
const participantMap = useMemo(
|
||||
() => new Map(availableParticipants.map((p) => [p.id, p])),
|
||||
[availableParticipants]
|
||||
);
|
||||
|
||||
if (over && active.id !== over.id) {
|
||||
const oldIndex = queue.findIndex((item) => item.id === active.id);
|
||||
const newIndex = queue.findIndex((item) => item.id === over.id);
|
||||
const queueIds = useMemo(() => queue.map((item) => item.id), [queue]);
|
||||
|
||||
const reorderedQueue = arrayMove(queue, oldIndex, newIndex);
|
||||
const participantIds = reorderedQueue.map((item) => item.participantId);
|
||||
onReorder(participantIds);
|
||||
}
|
||||
};
|
||||
const handleDragEnd = useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
if (over && active.id !== over.id) {
|
||||
const oldIndex = queue.findIndex((item) => item.id === active.id);
|
||||
const newIndex = queue.findIndex((item) => item.id === over.id);
|
||||
const reorderedQueue = arrayMove(queue, oldIndex, newIndex);
|
||||
onReorder(reorderedQueue.map((item) => item.participantId));
|
||||
}
|
||||
},
|
||||
[queue, onReorder]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
|
|
@ -177,24 +184,22 @@ export const QueueSection = memo(function QueueSection({
|
|||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
items={queue.map((item) => item.id)}
|
||||
items={queueIds}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<div className="space-y-1.5 mb-4">
|
||||
{queue.map((item, index) => {
|
||||
const participant = availableParticipants.find(
|
||||
(p) => p.id === item.participantId
|
||||
);
|
||||
const participant = participantMap.get(item.participantId);
|
||||
return (
|
||||
<SortableQueueItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
index={index}
|
||||
participantName={participant?.name || "Unknown"}
|
||||
participantName={participant?.name ?? "Unknown"}
|
||||
sportName={participant?.sport.name}
|
||||
canPick={canPick}
|
||||
onRemove={() => onRemoveFromQueue(item.id)}
|
||||
onDraft={onMakePick ? () => onMakePick(item.participantId) : undefined}
|
||||
onRemove={onRemoveFromQueue}
|
||||
onDraft={onMakePick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue