Skip to content

Fix drag position jump with memoized layout components#3616

Open
mattgperry wants to merge 1 commit intomainfrom
worktree-fix-issue-3169
Open

Fix drag position jump with memoized layout components#3616
mattgperry wants to merge 1 commit intomainfrom
worktree-fix-issue-3169

Conversation

@mattgperry
Copy link
Collaborator

Summary

  • Fixes drag position jumping when sibling layout changes happen while a memoized component (e.g. Ant Design Tree's rc-tree) is being dragged
  • When a dragged component is wrapped in React.memo, parent state changes don't trigger its willUpdate(), leaving the projection system without a snapshot — so layout compensation is silently skipped
  • The fix marks animation-blocked (dragged) nodes as layout-dirty before each update cycle and uses their previous layout as the snapshot, ensuring they always participate in measurement and receive correct drag compensation

Bug

Reported in #3169: dragging a file in an Ant Design Tree causes the element to jump to the wrong position when hovering over folders. The root cause is that rc-tree internally memoizes tree nodes — when a sibling's state changes (e.g., folder hover highlight), the dragged node doesn't re-render, so willUpdate() is never called. Without a snapshot, notifyLayoutUpdate() returns early and the drag's didUpdate listener never fires.

Fix

In create-projection-node.ts update(), before the transform reset step, iterate all nodes and for any that are isAnimationBlocked (set during drag) with a stale/missing snapshot:

  1. Copy the previous layout as the snapshot
  2. Set isLayoutDirty = true and shouldResetTransform = true

This ensures resetTransform()updateLayout()notifyLayoutUpdate() all process the node, computing the correct layout delta and emitting the didUpdate event for drag compensation.

Test plan

  • New Cypress E2E test: memoized drag component + sibling DOM insertion during drag
  • Test fails without fix, passes with fix (verified on both React 18 and React 19)
  • All 91 Jest test suites pass (762 tests)
  • All motion-dom tests pass (404 tests)
  • yarn build succeeds

Fixes #3169

🤖 Generated with Claude Code

…hanges

When a dragged element's component is memoized (e.g. by React.memo or
rc-tree's internal optimization), willUpdate() is never called during
sibling layout changes. This means the projection system has no snapshot,
skips measurement, and the drag position goes uncompensated — causing
the element to visually jump.

Fix: before the update cycle's transform reset, mark animation-blocked
(dragged) nodes as layout-dirty and use their previous layout as the
snapshot. This ensures they participate in the measurement cycle and
receive correct drag compensation via the didUpdate event.

Fixes #3169

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@greptile-apps
Copy link

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR fixes a drag position jump that occurs when a memoized layout component (e.g., rc-tree nodes in Ant Design) is being dragged and sibling layout changes occur. Because React.memo prevents re-renders, the projection node's willUpdate() is never called, leaving node.snapshot undefined; notifyLayoutUpdate then exits early and the drag's didUpdate compensation is never applied.

The fix introduces ensureDraggedNodesSnapshotted, called at the start of the update() write-cycle. For any node that is animation-blocked (dragged) but has no snapshot (!isLayoutDirty), it synthetically creates one by assigning node.snapshot = node.layout (the previous measured layout), then marks the node dirty so that resetTransform → updateLayout → notifyLayoutUpdate all process it normally.

Key implementation details:

  • Object reference semantics are correct: updateLayout replaces node.layout with a freshly allocated Measurements object, so node.snapshot still refers to the pre-update layout after measurement — exactly the "before" state needed to compute layout deltas.
  • The guard !node.isLayoutDirty reliably distinguishes memoized nodes (no willUpdate → no dirty flag → no snapshot) from nodes that did re-render.
  • isAnimationBlocked is exclusively set by VisualElementDragControls, so the fix is tightly scoped to drag interactions.

All existing tests pass and new E2E tests validate the fix across React 18 and 19.

Confidence Score: 5/5

  • This PR is safe to merge — the fix is tightly scoped to animation-blocked nodes that lack snapshots, with no impact on the broader projection pipeline.
  • The core fix is sound and well-implemented. It correctly uses object reference semantics (updateLayout replaces node.layout with a new object while snapshot retains the pre-update reference), the guard conditions reliably target only memoized drag nodes, and isAnimationBlocked is exclusively set during drag. All existing tests pass (91 Jest suites, 404 motion-dom tests) and new E2E tests validate the fix on both React 18 and 19. Code inspection shows no issues with the implementation.
  • No files require special attention.

Last reviewed commit: 8c977b1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Dragging animation doesn't correctly work with React 19 in StrictMode and Ant Design

1 participant