useDragAndDrop
A production-grade React hook to implement drag-and-drop sorting in lists, cards, and grids.
✅ Features
- Simple API for managing drag and drop behavior
- Fully typed with TypeScript
- Supports any item type via generics
- Easily integrates with any UI list or card system
📦 Usage
1import { useDragAndDrop } from './useDragAndDrop' 2 3const [items, setItems] = useState([ 4 { id: '1', data: 'Item 1' }, 5 { id: '2', data: 'Item 2' }, 6 { id: '3', data: 'Item 3' } 7]) 8 9const { listeners } = useDragAndDrop({ 10 items, 11 onDrop: setItems 12}) 13 14return ( 15 <div> 16 {items.map(item => ( 17 <div 18 key={item.id} 19 {...listeners(item)} 20 className="p-4 border rounded shadow my-2 bg-white" 21 > 22 {item.data} 23 </div> 24 ))} 25 </div> 26)
📋 Source Code
This is the full implementation of useDragAndDrop
1import { useRef, useState } from 'react'
2
3export interface DraggableItem<T = any> {
4 id: string
5 data: T
6}
7
8export interface UseDragAndDropProps<T = any> {
9 items: DraggableItem<T>[]
10 onDrop: (items: DraggableItem<T>[]) => void
11}
12
13export function useDragAndDrop<T>({ items, onDrop }: UseDragAndDropProps<T>) {
14 const [draggedItem, setDraggedItem] = useState<DraggableItem<T> | null>(null)
15 const dragOverItem = useRef<DraggableItem<T> | null>(null)
16
17 const handleDragStart = (item: DraggableItem<T>) => () => {
18 setDraggedItem(item)
19 }
20
21 const handleDragOver = (item: DraggableItem<T>) => (e: React.DragEvent) => {
22 e.preventDefault()
23 dragOverItem.current = item
24 }
25
26 const handleDrop = () => {
27 if (!draggedItem || !dragOverItem.current || draggedItem.id === dragOverItem.current.id) return
28
29 const draggedIndex = items.findIndex(i => i.id === draggedItem.id)
30 const dropIndex = items.findIndex(i => i.id === dragOverItem.current!.id)
31
32 const updatedItems = [...items]
33 const [removed] = updatedItems.splice(draggedIndex, 1)
34 updatedItems.splice(dropIndex, 0, removed)
35
36 onDrop(updatedItems)
37
38 setDraggedItem(null)
39 dragOverItem.current = null
40 }
41
42 return {
43 listeners: (item: DraggableItem<T>) => ({
44 draggable: true,
45 onDragStart: handleDragStart(item),
46 onDragOver: handleDragOver(item),
47 onDrop: handleDrop,
48 }),
49 }
50}
51