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