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 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 19:07:16 +03:00
parent b145d5416a
commit 27ef3bd694
2 changed files with 23 additions and 18 deletions

View File

@@ -118,21 +118,26 @@ export function ArrayEditor<T>({
} }
function onMouseUp() { function onMouseUp() {
setDragIndex((prevDrag) => { // Read current values from state updaters but defer onChange to avoid
setInsertAt((prevInsert) => { // calling parent setState during React's render/updater cycle
if (prevDrag !== null && prevInsert !== null) { let capturedDrag: number | null = null;
let targetIndex = prevInsert; let capturedInsert: number | null = null;
if (prevDrag < targetIndex) targetIndex -= 1;
if (prevDrag !== targetIndex) { setDragIndex((prev) => { capturedDrag = prev; return null; });
const updated = [...items]; setInsertAt((prev) => { capturedInsert = prev; return null; });
const [moved] = updated.splice(prevDrag, 1);
updated.splice(targetIndex, 0, moved); // Defer the reorder to next microtask so React finishes its batch first
onChange(updated); 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<T>({
key={i} key={i}
ref={(el) => { itemRefs.current[i] = el; }} ref={(el) => { itemRefs.current[i] = el; }}
onMouseDown={(e) => handleCardMouseDown(e, i)} 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"
> >
<div className="flex items-start justify-between gap-2 mb-3"> <div className="flex items-start justify-between gap-2 mb-3">
<div <div
@@ -203,7 +208,7 @@ export function ArrayEditor<T>({
key={i} key={i}
ref={(el) => { itemRefs.current[i] = el; }} ref={(el) => { itemRefs.current[i] = el; }}
onMouseDown={(e) => handleCardMouseDown(e, i)} 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"
> >
<div className="flex items-start justify-between gap-2 mb-3"> <div className="flex items-start justify-between gap-2 mb-3">
<div <div

View File

@@ -177,7 +177,7 @@ export default function TeamEditorPage() {
key={member.id} key={member.id}
ref={(el) => { itemRefs.current[i] = el; }} ref={(el) => { itemRefs.current[i] = el; }}
onMouseDown={(e) => handleCardMouseDown(e, i)} 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"
> >
<div <div
className="cursor-grab active:cursor-grabbing text-neutral-500 hover:text-white transition-colors select-none" className="cursor-grab active:cursor-grabbing text-neutral-500 hover:text-white transition-colors select-none"
@@ -237,7 +237,7 @@ export default function TeamEditorPage() {
key={member.id} key={member.id}
ref={(el) => { itemRefs.current[i] = el; }} ref={(el) => { itemRefs.current[i] = el; }}
onMouseDown={(e) => handleCardMouseDown(e, i)} 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"
> >
<div <div
className="cursor-grab active:cursor-grabbing text-neutral-500 hover:text-white transition-colors select-none" className="cursor-grab active:cursor-grabbing text-neutral-500 hover:text-white transition-colors select-none"