Skip to content

feat: migrate dnd-kit story to @dnd-kit/react#869

Open
vimzh wants to merge 2 commits intoinokawa:mainfrom
vimzh:feat/migrate-dndkit-react
Open

feat: migrate dnd-kit story to @dnd-kit/react#869
vimzh wants to merge 2 commits intoinokawa:mainfrom
vimzh:feat/migrate-dndkit-react

Conversation

@vimzh
Copy link
Copy Markdown
Contributor

@vimzh vimzh commented Mar 14, 2026

Summary

Migrates the dnd-kit Storybook story from @dnd-kit/core + @dnd-kit/sortable + @dnd-kit/utilities to @dnd-kit/react + @dnd-kit/helpers, following the official migration guide.

Closes #852

Changes

Dependencies (package.json):

  • Removed: @dnd-kit/core, @dnd-kit/sortable
  • Added: @dnd-kit/react, @dnd-kit/helpers

Story (stories/react/advanced/With dnd-kit.stories.tsx):

  • DndContextDragDropProvider
  • SortableContext + verticalListSortingStrategy → removed (automatic registration)
  • useSensors/useSensor/closestCenter → removed (built-in defaults)
  • useSortable destructured {attributes, listeners, setNodeRef, transform, transition} → unified {ref, isDragging}
  • CSS.Transform.toString(transform) → automatic (handled internally)
  • forwardRef Item + SortableItem → single SortableItem component
  • useState(activeId) + onDragStart/onDragEnd state management → DragOverlay render function pattern
  • arrayMove + manual indexOfmove(items, event) from @dnd-kit/helpers
  • Extracted shared item styling to a itemStyle constant (matches Chat.stories.tsx convention)

The net result is 120 → 59 lines — a significantly simpler story that demonstrates the same drag-and-drop + virtual scrolling integration.

Test plan

  • npm test — all 206 tests pass
  • npm run lint — 0 errors
  • npm run format:ci — clean
  • npx tsc --noEmit — no type errors
  • Manual: run npm run storybook, navigate to "With dnd-kit" story, verify drag-and-drop reordering works within the virtual list

Replace @dnd-kit/core + @dnd-kit/sortable with @dnd-kit/react +
@dnd-kit/helpers following the official migration guide.

Key changes:
- DndContext → DragDropProvider
- SortableContext removed (automatic registration)
- useSortable returns unified {ref, isDragging}
- DragOverlay uses render function (no more activeId state)
- move() helper replaces manual arrayMove + indexOf

Closes inokawa#852
Copy link
Copy Markdown
Owner

@inokawa inokawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, but it seems like dnd is broken.

@dnd-kit/core

2026-03-15.22.00.25.mov

@dnd-kit/react

2026-03-15.22.02.08.mov

@vimzh
Copy link
Copy Markdown
Contributor Author

vimzh commented Mar 15, 2026

Thanks for the review!
the root cause is an architectural incompatibility between @dnd-kit/react (v0.3.x) and virtualization:
Old API: SortableContext maintains a centralized registry of all items by ID, independent of DOM presence. When VList unmounts offscreen items, SortableContext still knows about them, so collision detection continues to work.
New API (@dnd-kit/react): There's no SortableContext. Items self-register/unregister via useSortable → useInstance(register). When VList unmounts an offscreen item, the Sortable instance calls unregister() and is completely removed from the DragDropManager. During a drag, only the ~15-20 currently-mounted items exist in the manager, so collision detection and drop targets break.
This appears to be a limitation in @dnd-kit/react v0.3.x itself, not sure on how to proceed should I close this PR?

thanks!

@inokawa
Copy link
Copy Markdown
Owner

inokawa commented Mar 15, 2026

Many users uses dnd-kit with virtualization clauderic/dnd-kit#1372. It might be a good idea to provide feedback to dnd-kit repo.

@vimzh
Copy link
Copy Markdown
Contributor Author

vimzh commented Mar 15, 2026

Thanks for the suggestion! I investigated a few bypass approaches to make it work:

  1. Patching registry.unregister during drag (same Proxy pattern DragOverlay uses internally) this prevents items from unregistering when virtua unmounts them. But droppables without DOM elements cause ghost gaps in the layout since dnd-kit can't calculate their positions.
  2. Deferring unregistrations until after drop, the items get stuck in drag state because the deferred cleanup interferes with dnd-kit's drop finalization.
  3. Using keepMounted to mount all items during drag, works but defeats the purpose of virtualization.

@dnd-kit/react v0.3.x ties registration to component lifecycle (useSortable → useInstance → useLayoutEffect cleanup).
The old SortableContext from @dnd-kit/core solved this by maintaining a centralized ID registry independent of DOM presence.

I'll open an issue/discussion on the dnd-kit repo. For now, should I close this PR or keep it open? thanks!

onDragEnd alone breaks with virtualized lists because unmounted items
unregister from dnd-kit's collision detection. Live reordering via
onDragOver keeps drop targets among mounted items. Matches the pattern
from dnd-kit's official virtualization examples.
@vimzh
Copy link
Copy Markdown
Contributor Author

vimzh commented Mar 15, 2026

Updated the story to use onDragOver for live reordering instead of onDragEnd. This fixes the virtualization issue, the dnd-kit maintainer pointed out that their official virtualization examples use this pattern so that drop targets are always among mounted items. Also added snapshot restore on cancel and reused the existing range() utility.

thanks!

@vimzh vimzh requested a review from inokawa March 16, 2026 20:18
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.

Migrate dnd-kit story to @dnd-kit/react

2 participants