From c499fb9595f9a4b47084b302a9193eb7b12fa2c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Mar 2026 22:54:28 +0000 Subject: [PATCH] =?UTF-8?q?Fix=20O(n=C2=B2)=20lookup,=20memoize=20componen?= =?UTF-8?q?ts=20and=20callbacks=20in=20QueueSection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Build a participantMap (Map) 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 --- app/components/draft/QueueSection.tsx | 53 +++++++++++++++------------ 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/app/components/draft/QueueSection.tsx b/app/components/draft/QueueSection.tsx index 8011aa0..6fbb38e 100644 --- a/app/components/draft/QueueSection.tsx +++ b/app/components/draft/QueueSection.tsx @@ -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 @@ -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" > × @@ -126,7 +126,7 @@ function SortableQueueItem({ ); -} +}); 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 (
@@ -177,24 +184,22 @@ export const QueueSection = memo(function QueueSection({ onDragEnd={handleDragEnd} > item.id)} + items={queueIds} strategy={verticalListSortingStrategy} >
{queue.map((item, index) => { - const participant = availableParticipants.find( - (p) => p.id === item.participantId - ); + const participant = participantMap.get(item.participantId); return ( onRemoveFromQueue(item.id)} - onDraft={onMakePick ? () => onMakePick(item.participantId) : undefined} + onRemove={onRemoveFromQueue} + onDraft={onMakePick} /> ); })}