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:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user