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 { GripVertical } from "lucide-react";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Badge } from "~/components/ui/badge";
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
|
@ -47,7 +47,7 @@ interface QueueSectionProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sortable queue item component
|
// Sortable queue item component
|
||||||
function SortableQueueItem({
|
const SortableQueueItem = memo(function SortableQueueItem({
|
||||||
item,
|
item,
|
||||||
index,
|
index,
|
||||||
participantName,
|
participantName,
|
||||||
|
|
@ -61,8 +61,8 @@ function SortableQueueItem({
|
||||||
participantName: string;
|
participantName: string;
|
||||||
sportName?: string;
|
sportName?: string;
|
||||||
canPick: boolean;
|
canPick: boolean;
|
||||||
onRemove: () => void;
|
onRemove: (queueId: string) => void;
|
||||||
onDraft?: () => void;
|
onDraft?: (participantId: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
attributes,
|
attributes,
|
||||||
|
|
@ -109,7 +109,7 @@ function SortableQueueItem({
|
||||||
variant="default"
|
variant="default"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-7 text-xs bg-electric text-background hover:bg-electric/90"
|
className="h-7 text-xs bg-electric text-background hover:bg-electric/90"
|
||||||
onClick={onDraft}
|
onClick={() => onDraft(item.participantId)}
|
||||||
>
|
>
|
||||||
Draft
|
Draft
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -118,7 +118,7 @@ function SortableQueueItem({
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-7 w-7 text-destructive hover:text-destructive hover:bg-destructive/10"
|
className="h-7 w-7 text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||||
onClick={onRemove}
|
onClick={() => onRemove(item.id)}
|
||||||
title="Remove from queue"
|
title="Remove from queue"
|
||||||
>
|
>
|
||||||
<span className="text-lg">×</span>
|
<span className="text-lg">×</span>
|
||||||
|
|
@ -126,7 +126,7 @@ function SortableQueueItem({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
export const QueueSection = memo(function QueueSection({
|
export const QueueSection = memo(function QueueSection({
|
||||||
queue,
|
queue,
|
||||||
|
|
@ -150,18 +150,25 @@ export const QueueSection = memo(function QueueSection({
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDragEnd = (event: DragEndEvent) => {
|
const participantMap = useMemo(
|
||||||
const { active, over } = event;
|
() => new Map(availableParticipants.map((p) => [p.id, p])),
|
||||||
|
[availableParticipants]
|
||||||
|
);
|
||||||
|
|
||||||
if (over && active.id !== over.id) {
|
const queueIds = useMemo(() => queue.map((item) => item.id), [queue]);
|
||||||
const oldIndex = queue.findIndex((item) => item.id === active.id);
|
|
||||||
const newIndex = queue.findIndex((item) => item.id === over.id);
|
|
||||||
|
|
||||||
const reorderedQueue = arrayMove(queue, oldIndex, newIndex);
|
const handleDragEnd = useCallback(
|
||||||
const participantIds = reorderedQueue.map((item) => item.participantId);
|
(event: DragEndEvent) => {
|
||||||
onReorder(participantIds);
|
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 (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
|
|
@ -177,24 +184,22 @@ export const QueueSection = memo(function QueueSection({
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
>
|
>
|
||||||
<SortableContext
|
<SortableContext
|
||||||
items={queue.map((item) => item.id)}
|
items={queueIds}
|
||||||
strategy={verticalListSortingStrategy}
|
strategy={verticalListSortingStrategy}
|
||||||
>
|
>
|
||||||
<div className="space-y-1.5 mb-4">
|
<div className="space-y-1.5 mb-4">
|
||||||
{queue.map((item, index) => {
|
{queue.map((item, index) => {
|
||||||
const participant = availableParticipants.find(
|
const participant = participantMap.get(item.participantId);
|
||||||
(p) => p.id === item.participantId
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<SortableQueueItem
|
<SortableQueueItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
index={index}
|
index={index}
|
||||||
participantName={participant?.name || "Unknown"}
|
participantName={participant?.name ?? "Unknown"}
|
||||||
sportName={participant?.sport.name}
|
sportName={participant?.sport.name}
|
||||||
canPick={canPick}
|
canPick={canPick}
|
||||||
onRemove={() => onRemoveFromQueue(item.id)}
|
onRemove={onRemoveFromQueue}
|
||||||
onDraft={onMakePick ? () => onMakePick(item.participantId) : undefined}
|
onDraft={onMakePick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue