Skip to content

Commit

Permalink
Synchronize isDragSource from true to false synchronously
Browse files Browse the repository at this point in the history
  • Loading branch information
clauderic committed Feb 4, 2025
1 parent 9d71078 commit c5f25c8
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/should-update-synchronously.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@dnd-kit/react': patch
---

Force synchronous re-render when `isDragSource` property is updated from `true` to `false` to enable seamless transition into idle state after drop animation. Without this change, the drop animation can finish before React has had a chance to update the drag source styles back to its idle state, which can cause some flickering.
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ function SortableItem({
group,
accept: 'item',
type: 'item',
feedback: 'clone',
index,
data: {group},
});
Expand Down
9 changes: 8 additions & 1 deletion packages/react/src/core/draggable/useDraggable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function useDraggable<T extends Data = Data>(
manager
)
);
const trackedDraggable = useDeepSignal(draggable);
const trackedDraggable = useDeepSignal(draggable, shouldUpdateSynchronously);

useOnValueChange(id, () => (draggable.id = id));
useOnElementChange(handle, (handle) => (draggable.handle = handle));
Expand Down Expand Up @@ -87,3 +87,10 @@ export function useDraggable<T extends Data = Data>(
),
};
}

function shouldUpdateSynchronously(key: string, oldValue: any, newValue: any) {
// Update synchronously after drop animation
if (key === 'isDragSource' && !newValue && oldValue) return true;

return false;
}
10 changes: 8 additions & 2 deletions packages/react/src/hooks/useDeepSignal.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {useMemo, useRef} from 'react';
import {flushSync} from 'react-dom';
import {effect, untracked} from '@dnd-kit/state';

import {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect.ts';
import {useForceUpdate} from './useForceUpdate.ts';

/** Trigger a re-render when reading signal properties of an object. */
export function useDeepSignal<T extends object | null | undefined>(
target: T
target: T,
synchronous?: (property: keyof T, oldValue: any, newValue: any) => boolean
): T {
const tracked = useRef(new Map<string | symbol, any>());
const forceUpdate = useForceUpdate();
Expand All @@ -19,6 +21,7 @@ export function useDeepSignal<T extends object | null | undefined>(

return effect(() => {
let stale = false;
let sync = false;

for (const entry of tracked.current) {
const [key] = entry;
Expand All @@ -28,10 +31,13 @@ export function useDeepSignal<T extends object | null | undefined>(
if (value !== latestValue) {
stale = true;
tracked.current.set(key, latestValue);
sync = synchronous?.(key as keyof T, value, latestValue) ?? false;
}
}

if (stale) forceUpdate();
if (stale) {
sync ? flushSync(forceUpdate) : forceUpdate();
}
});
}, [target]);

Expand Down
10 changes: 8 additions & 2 deletions packages/react/src/sortable/useSortable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {Sortable, defaultSortableTransition} from '@dnd-kit/dom/sortable';
import type {SortableInput} from '@dnd-kit/dom/sortable';
import {useInstance} from '@dnd-kit/react';
import {
useComputed,
useImmediateEffect as immediateEffect,
useIsomorphicLayoutEffect,
useOnValueChange,
Expand Down Expand Up @@ -54,7 +53,7 @@ export function useSortable<T extends Data = Data>(input: UseSortableInput<T>) {
);
});

const trackedSortable = useDeepSignal(sortable);
const trackedSortable = useDeepSignal(sortable, shouldUpdateSynchronously);

useOnValueChange(id, () => (sortable.id = id));

Expand Down Expand Up @@ -168,3 +167,10 @@ export function useSortable<T extends Data = Data>(input: UseSortableInput<T>) {
),
};
}

function shouldUpdateSynchronously(key: string, oldValue: any, newValue: any) {
// Update synchronously after drop animation
if (key === 'isDragSource' && !newValue && oldValue) return true;

return false;
}

0 comments on commit c5f25c8

Please sign in to comment.