Skip to content

fix(dom): restore DOM elements on dragend to prevent removeChild crash - #2102

Open
ElitoSear wants to merge 2 commits into
clauderic:mainfrom
ElitoSear:fix/dom-reconciliation-crash
Open

ElitoSear wants to merge 2 commits into
clauderic:mainfrom
ElitoSear:fix/dom-reconciliation-crash

Conversation

@ElitoSear

Copy link
Copy Markdown

Summary

This PR fixes issues #1747 and #1780 where the application crashes with DOMException: Node.removeChild: The node to be removed is not a child of this node (or NotFoundError on Vite/Rollup) when dragging sortable items or elements across different groups/containers.

Cause of the Issue

During dragging operations, OptimisticSortingPlugin and Feedback directly manipulate the DOM tree (e.g., using insertAdjacentElement to reorder nodes, or appending dragging elements to a popover/overlay root element). When a drag operation ends, the framework (React/Solid/Vue/Svelte) updates state and reconciles the DOM tree. However, because the DOM structure was altered directly outside the framework, the virtual DOM reconciliation gets out of sync, leading to errors like parent.removeChild failing to find the target element.

Solution

  1. OptimisticSortingPlugin:
    • Capture the original parent and sibling elements of the dragging element on dragstart.
    • On dragend, always restore the dragging element back to its original parent and position synchronously before the framework reconciles the DOM.
  2. Feedback:
    • Detect if the feedback element is the real element itself (i.e. not using a separate DragOverlay).
    • If not using a separate DragOverlay, bypass the asynchronous drop animation and synchronously clean up / restore the element's original position on dragend to ensure a clean DOM state for the renderer's reconciliation tick.

This completely avoids any DOM reconciliation mismatches and resolves the crashes across all list component structures.

@changeset-bot

changeset-bot Bot commented Jul 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 17876fa

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@dnd-kit/dom Patch
@dnd-kit/abstract Patch
@dnd-kit/collision Patch
@dnd-kit/geometry Patch
@dnd-kit/helpers Patch
@dnd-kit/react Patch
@dnd-kit/state Patch
@dnd-kit/vue Patch
@dnd-kit/solid Patch
@dnd-kit/svelte Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…on-overlay elements

Guards against the drop-animation effect not firing in sync with the
dragend event, and makes cleanup idempotent since it can now be
triggered from two separate listeners.
@pkg-pr-new

pkg-pr-new Bot commented Jul 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@dnd-kit/abstract

npm i https://pkg.pr.new/@dnd-kit/abstract@2102

@dnd-kit/collision

npm i https://pkg.pr.new/@dnd-kit/collision@2102

@dnd-kit/dom

npm i https://pkg.pr.new/@dnd-kit/dom@2102

@dnd-kit/geometry

npm i https://pkg.pr.new/@dnd-kit/geometry@2102

@dnd-kit/helpers

npm i https://pkg.pr.new/@dnd-kit/helpers@2102

@dnd-kit/react

npm i https://pkg.pr.new/@dnd-kit/react@2102

@dnd-kit/solid

npm i https://pkg.pr.new/@dnd-kit/solid@2102

@dnd-kit/state

npm i https://pkg.pr.new/@dnd-kit/state@2102

@dnd-kit/svelte

npm i https://pkg.pr.new/@dnd-kit/svelte@2102

@dnd-kit/vue

npm i https://pkg.pr.new/@dnd-kit/vue@2102

commit: 17876fa

FaroKlandee added a commit to FaroKlandee/Project-noBackLog that referenced this pull request Aug 6, 2026
Two independent bugs surfaced during cross-list drag testing:

- Same-list reorders duplicated the dragged card: CardItem passed
  group: card.listId (a number) into useSortable, but @dnd-kit/helpers's
  move() compares group against cardsByList's Object.keys with !==,
  and object keys are always strings. Coerced to String(card.listId).

- Cross-list drops threw "NotFoundError: Failed to execute 'removeChild'
  on 'Node'": an upstream bug in @dnd-kit/dom's OptimisticSortingPlugin
  physically relocates the dragged DOM node on every dragover as a live
  preview, and never restores it on a successful drop, so React's
  reconciler and the live DOM disagree about the node's parent on the
  next render. Backported the fix from the still-unmerged upstream PR
  (clauderic/dnd-kit#2102) via patch-package so it survives npm install.

Also added a DragOverlay (with minimal CardPreview/ListColumnPreview
clones) so dnd-kit's drag feedback targets a React-owned node instead
of the real card/column element, and a .claude/launch.json for running
both dev servers locally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@DJ1TJOO

DJ1TJOO commented Aug 9, 2026

Copy link
Copy Markdown

Any update on this?

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.

2 participants