Q&A
How to drag and drop using React
Overview
Drag and drop lets users grab an element and move it - reordering lists, moving cards between columns (kanban), uploading files. In React you wire the browser's drag events to state updates. The native API works for basic needs but is fiddly and weak on touch/accessibility, so production apps usually reach for a library.
graph LR
A[onDragStart: record dragged item] --> B[onDragOver: preventDefault to allow drop]
B --> C[onDrop: read data, update state]
C --> D[React re-renders new order]
style C fill:#1e1b4b,color:#fff
Native HTML5 Drag and Drop
Make an element draggable and handle the events:
import { useState } from "react";
function DragList() {
const [items, setItems] = useState(["A", "B", "C", "D"]);
const [dragIndex, setDragIndex] = useState(null);
const onDrop = (dropIndex) => {
if (dragIndex === null || dragIndex === dropIndex) return;
const next = [...items];
const [moved] = next.splice(dragIndex, 1); // remove dragged
next.splice(dropIndex, 0, moved); // insert at target
setItems(next);
setDragIndex(null);
};
return (
<ul>
{items.map((item, i) => (
<li
key={item}
draggable
onDragStart={() => setDragIndex(i)}
onDragOver={(e) => e.preventDefault()} // REQUIRED to allow a drop
onDrop={() => onDrop(i)}
style={{ padding: 8, border: "1px solid #ccc", cursor: "grab" }}
>
{item}
</li>
))}
</ul>
);
}
The non-obvious essential: you must call e.preventDefault() in onDragOver, otherwise onDrop never fires (by default elements aren't valid drop targets).
Passing Data via dataTransfer
The native API also has a dataTransfer object to carry payload between drag and drop:
onDragStart={(e) => e.dataTransfer.setData("text/plain", item)}
onDrop={(e) => {
const data = e.dataTransfer.getData("text/plain");
// use data
}}
This is especially useful for dragging between different drop zones or from outside (e.g. files via e.dataTransfer.files).
Using a Library (recommended for real apps)
@dnd-kit (modern, accessible, the current go-to)
import { DndContext, closestCenter } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy, useSortable } from "@dnd-kit/sortable";
// Wrap items in DndContext + SortableContext; useSortable gives drag props per item.
// Handle onDragEnd to reorder your state array via arrayMove(...)
@dnd-kit provides keyboard support, touch sensors, collision detection, and accessibility out of the box.
Library comparison
| Library | Best for | Notes |
|---|---|---|
| @dnd-kit | General, accessible DnD | Modern, modular, keyboard + touch |
| @hello-pangea/dnd | Vertical/horizontal lists | Maintained fork of react-beautiful-dnd |
| react-dnd | Complex custom interactions | Powerful, HTML5 backend, steeper API |
Accessibility and Touch
The native HTML5 API has poor touch support and no keyboard story. If users on mobile or assistive tech matter (they usually do), a library like @dnd-kit that supports pointer/touch sensors and keyboard dragging is the responsible choice.
Why This Matters in Interviews
Front-end interviews use this to test event handling and state-driven reordering. The must-know native detail is calling e.preventDefault() in onDragOver so onDrop fires; the must-know judgment is that the native API is weak on touch/accessibility, so production reorder/kanban features use a library (@dnd-kit is the current standard). Reordering via immutable array ops (splice on a copy) demonstrates correct React state handling.
Common Pitfalls
- Forgetting
e.preventDefault()inonDragOver→onDropsilently never fires. - Mutating the items array in place instead of creating a new array → no re-render.
- Using array index as
keywhile reordering (causes glitches) - use a stable id. - Relying on the native API for mobile/keyboard users (poor support) - use a library.
References
- MDN HTML Drag and Drop API: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
- dnd kit: https://dndkit.com/