From 27ef3bd6949735ce424864b8f889d925eca1d4b9 Mon Sep 17 00:00:00 2001 From: "diana.dolgolyova" Date: Wed, 11 Mar 2026 19:07:16 +0300 Subject: [PATCH] fix: setState-during-render error + hover highlight on cards Defer onChange call in ArrayEditor drag drop to queueMicrotask to avoid calling parent setState inside React updater. Add hover highlight on draggable cards for better visual feedback. Co-Authored-By: Claude Opus 4.6 --- src/app/admin/_components/ArrayEditor.tsx | 37 +++++++++++++---------- src/app/admin/team/page.tsx | 4 +-- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/app/admin/_components/ArrayEditor.tsx b/src/app/admin/_components/ArrayEditor.tsx index f58b57b..8e6c2ac 100644 --- a/src/app/admin/_components/ArrayEditor.tsx +++ b/src/app/admin/_components/ArrayEditor.tsx @@ -118,21 +118,26 @@ export function ArrayEditor({ } function onMouseUp() { - setDragIndex((prevDrag) => { - setInsertAt((prevInsert) => { - if (prevDrag !== null && prevInsert !== null) { - let targetIndex = prevInsert; - if (prevDrag < targetIndex) targetIndex -= 1; - if (prevDrag !== targetIndex) { - const updated = [...items]; - const [moved] = updated.splice(prevDrag, 1); - updated.splice(targetIndex, 0, moved); - onChange(updated); - } + // Read current values from state updaters but defer onChange to avoid + // calling parent setState during React's render/updater cycle + let capturedDrag: number | null = null; + let capturedInsert: number | null = null; + + setDragIndex((prev) => { capturedDrag = prev; return null; }); + setInsertAt((prev) => { capturedInsert = prev; return null; }); + + // Defer the reorder to next microtask so React finishes its batch first + queueMicrotask(() => { + if (capturedDrag !== null && capturedInsert !== null) { + let targetIndex = capturedInsert; + if (capturedDrag < targetIndex) targetIndex -= 1; + if (capturedDrag !== targetIndex) { + const updated = [...items]; + const [moved] = updated.splice(capturedDrag, 1); + updated.splice(targetIndex, 0, moved); + onChange(updated); } - return null; - }); - return null; + } }); } @@ -152,7 +157,7 @@ export function ArrayEditor({ key={i} ref={(el) => { itemRefs.current[i] = el; }} onMouseDown={(e) => handleCardMouseDown(e, i)} - className="rounded-lg border border-white/10 bg-neutral-900/50 p-4 mb-3" + className="rounded-lg border border-white/10 bg-neutral-900/50 p-4 mb-3 hover:border-white/25 hover:bg-neutral-800/50 transition-colors" >
({ key={i} ref={(el) => { itemRefs.current[i] = el; }} onMouseDown={(e) => handleCardMouseDown(e, i)} - className="rounded-lg border border-white/10 bg-neutral-900/50 p-4 mb-3" + className="rounded-lg border border-white/10 bg-neutral-900/50 p-4 mb-3 hover:border-white/25 hover:bg-neutral-800/50 transition-colors" >
{ itemRefs.current[i] = el; }} onMouseDown={(e) => handleCardMouseDown(e, i)} - className="flex items-center gap-4 rounded-lg border border-white/10 bg-neutral-900/50 p-3 mb-2" + className="flex items-center gap-4 rounded-lg border border-white/10 bg-neutral-900/50 p-3 mb-2 hover:border-white/25 hover:bg-neutral-800/50 transition-colors" >
{ itemRefs.current[i] = el; }} onMouseDown={(e) => handleCardMouseDown(e, i)} - className="flex items-center gap-4 rounded-lg border border-white/10 bg-neutral-900/50 p-3 mb-2" + className="flex items-center gap-4 rounded-lg border border-white/10 bg-neutral-900/50 p-3 mb-2 hover:border-white/25 hover:bg-neutral-800/50 transition-colors" >