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() {
setDragIndex((prevDrag) => {
setInsertAt((prevInsert) => {
if (prevDrag !== null && prevInsert !== null) {
let targetIndex = prevInsert;
if (prevDrag < targetIndex) targetIndex -= 1;
if (prevDrag !== targetIndex) {
// 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(prevDrag, 1);
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}
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"
>
<div className="flex items-start justify-between gap-2 mb-3">
<div
@@ -203,7 +208,7 @@ export function ArrayEditor<T>({
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"
>
<div className="flex items-start justify-between gap-2 mb-3">
<div

View File

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