Build a drag-to-reorder list
mediumUI ComponentsHigh freq+220 XP
Build a list of items that can be reordered by dragging. Each item is a `<li>` that can be dragged onto any other item in the list.
On drop, the dragged item moves to the position of the drop target, and every other item shifts to preserve order.
Use the native HTML5 drag-and-drop API — `draggable`, `onDragStart`, `onDragOver`, `onDrop`.
Examples
// Initial: [Backlog, In Progress, Review, Done]
// item-0 item-1 item-2 item-3
// Drag item-0 ("Backlog") onto item-3 ("Done")
// → [In Progress, Review, Done, Backlog]
// item-0 is now "In Progress", item-3 is now "Backlog"
// Drop an item on itself → no change.testids reindex after a reorder — item-0 is always whatever currently sits at position 0.
function onDrop(toIndex) {
if (draggedFrom === null || draggedFrom === toIndex) return;
setItems((prev) => {
const next = [...prev];
const [moved] = next.splice(draggedFrom, 1);
next.splice(toIndex, 0, moved);
return next;
});
setDraggedFrom(null);
}Splice out, splice in; immutable copy so React re-renders.
What it has to do
- The ordered list is `<ul>` with items as `<li data-testid="item-{index}">` — testid is the item's current position in the list, not its id.
- Every item has `draggable="true"` and renders its text as plain text.
- On drop, the dragged item moves to the drop target's position; every other item shifts accordingly.
- After a reorder, the `data-testid="item-N"` sequence must reflect the new order (so other code and tests can address items by their current position).
- The default drag preview is fine — no custom ghost needed.
Notes
- `onDragOver` MUST call `e.preventDefault()` — without it, the browser refuses to fire the drop event. The single most common bug on this problem.
- Keep the source index in React state, not `e.dataTransfer` — `dataTransfer` is write-only inside `dragstart` on some browsers and awkward to test.
- Native HTML5 DnD doesn't work on touch devices at all — a real reorder list needs a pointer-event fallback (or a library like dnd-kit) for mobile.
- Accessibility: provide a keyboard reorder (e.g. focus an item, press Space to pick up, arrows to move, Space to drop) — HTML5 DnD is mouse-only.
Stuck? The react course covers the ideas this problem is built on.
import { useState } from "react"; const INITIAL = ["Backlog", "In Progress", "Review", "Done"]; export default function App() { const [items, setItems] = useState(INITIAL); const [draggedFrom, setDraggedFrom] = useState(null); // TODO: implement onDragStart (remember the source index), onDragOver // (allow the drop), and onDrop (splice the dragged item out and in at // the target). Reset state on dragend. return ( <ul style={{ fontFamily: "sans-serif", padding: 16, listStyle: "none" }}> {items.map((item, index) => ( <li key={item} data-testid={`item-${index}`} draggable style={{ padding: 10, border: "1px solid #ddd", margin: "4px 0", cursor: "grab" }} > {item} </li> ))} </ul> ); }
- 1.The ordered list is `<ul>` with items as `<li data-testid="item-{index}">` — testid is the item's current position in the list, not its id.
- 2.Every item has `draggable="true"` and renders its text as plain text.
- 3.On drop, the dragged item moves to the drop target's position; every other item shifts accordingly.
- 4.After a reorder, the `data-testid="item-N"` sequence must reflect the new order (so other code and tests can address items by their current position).
- 5.The default drag preview is fine — no custom ghost needed.
4 automated assertions run against your rendered component. The exact assertion names are hidden so you build to the behaviour, not to the test.
4 tests · +220 XP
Discussion
Sign in to postSign in to join the conversation.Sign in
No comments yet. Be the first to say something.