javascript
63 lines · 8 steps
Drag-to-reorder lists in React
A React component that lets users reorder list items with the native HTML drag-and-drop API and three pieces of state.
Explained by
highlit
1import { useState } from 'react';
2
3export function ReorderableList({ initialItems }) {
4 const [items, setItems] = useState(initialItems);
5 const [draggingId, setDraggingId] = useState(null);
6 const [overId, setOverId] = useState(null);
7
8 const handleDragStart = (event, id) => {
9 setDraggingId(id);
10 event.dataTransfer.effectAllowed = 'move';
11 event.dataTransfer.setData('text/plain', String(id));
12 };
13
14 const handleDragOver = (event, id) => {
15 event.preventDefault();
16 event.dataTransfer.dropEffect = 'move';
17 if (id !== overId) setOverId(id);
18 };
19
20 const handleDrop = (event, targetId) => {
21 event.preventDefault();
22 if (draggingId === null || draggingId === targetId) return;
23
24 setItems((prev) => {
25 const next = [...prev];
26 const from = next.findIndex((item) => item.id === draggingId);
27 const to = next.findIndex((item) => item.id === targetId);
28 const [moved] = next.splice(from, 1);
29 next.splice(to, 0, moved);
30 return next;
31 });
32 };
33
34 const handleDragEnd = () => {
35 setDraggingId(null);
36 setOverId(null);
37 };
38
39 return (
40 <ul className="reorderable-list">
41 {items.map((item) => (
42 <li
43 key={item.id}
44 draggable
45 onDragStart={(e) => handleDragStart(e, item.id)}
46 onDragOver={(e) => handleDragOver(e, item.id)}
47 onDrop={(e) => handleDrop(e, item.id)}
48 onDragEnd={handleDragEnd}
49 className={[
50 'reorderable-item',
51 draggingId === item.id && 'is-dragging',
52 overId === item.id && draggingId !== item.id && 'is-over',
53 ]
54 .filter(Boolean)
55 .join(' ')}
56 >
57 <span className="drag-handle" aria-hidden="true">≡</span>
58 {item.label}
59 </li>
60 ))}
61 </ul>
62 );
63}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Tracking the dragged item and hover target in state lets you compute both the reorder and the visual feedback.
- 2Reordering immutably with a copied array plus splice keeps React's state updates predictable.
- 3The native HTML drag-and-drop API needs preventDefault on dragover and drop for a drop to register.
Related explainers
javascript
import { useCallback, useEffect, useState } from 'react'; export function useLocalStorage(key, initialValue) { const readValue = useCallback(() => {
How a useLocalStorage hook syncs state in React
custom hooks
localstorage
state persistence
Intermediate
8 steps
javascript
import { unstable_cache, revalidateTag } from 'next/cache' import { db } from '@/lib/db' export const getDashboardStats = unstable_cache(
Caching dashboard stats in Next.js
caching
cache-invalidation
tag-based-revalidation
Intermediate
8 steps
javascript
import { Component } from 'react'; import { reportError } from './services/telemetry'; export class ErrorBoundary extends Component {
How a React ErrorBoundary works
error-handling
lifecycle-methods
render-props
Intermediate
8 steps
typescript
import { useCallback, useEffect, useRef, useState } from "react"; interface UseResendCooldownOptions { cooldownSeconds?: number;
A resend cooldown hook in React
custom-hooks
timers
state-management
Intermediate
7 steps
javascript
const FOCUSABLE = [ 'a[href]', 'button:not([disabled])', 'input:not([disabled])',
How to trap keyboard focus in a dialog
accessibility
dom
event-handling
Intermediate
8 steps
javascript
function generateCalendarGrid(year, month) { const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const daysInMonth = lastDay.getDate();
Building a text calendar in JavaScript
date-handling
grid-layout
modular-arithmetic
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/drag-to-reorder-lists-in-react-explained-javascript-1e20/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.