In modern web applications, providing a smooth user interface is everything. Users in the US expect intuitive interactions, like moving tasks in a project management tool or organizing a photo gallery.
I’ve been building React applications for over eight years, and adding drag-and-drop functionality is one of the most requested features I encounter.
In this tutorial, I will show you how to build a React drag-and-drop component using three different professional methods.
Method 1: Use the Native HTML5 Drag and Drop API
When I first started, I used the native HTML5 API because it doesn’t require any external libraries. It is lightweight and great for simple tasks like moving items between two lists.
In this example, we will create a “Project Task Board” where you can move tasks between “To Do” and “Completed” status.
import React, { useState } from 'react';
const TaskBoard = () => {
const [tasks, setTasks] = useState([
{ id: '1', content: 'Review Q1 Marketing Budget', status: 'todo' },
{ id: '2', content: 'Update AWS Infrastructure', status: 'todo' },
{ id: '3', content: 'Client Meeting: New York Office', status: 'todo' },
{ id: '4', content: 'File Quarterly Taxes', status: 'completed' },
]);
const onDragStart = (e, id) => {
e.dataTransfer.setData('id', id);
};
const onDragOver = (e) => {
e.preventDefault();
};
const onDrop = (e, newStatus) => {
const id = e.dataTransfer.getData('id');
const updatedTasks = tasks.map((task) => {
if (task.id === id) {
return { ...task, status: newStatus };
}
return task;
});
setTasks(updatedTasks);
};
const renderTasks = (status) => {
return tasks
.filter((task) => task.status === status)
.map((task) => (
<div
key={task.id}
draggable
onDragStart={(e) => onDragStart(e, task.id)}
style={{
padding: '10px',
margin: '5px 0',
backgroundColor: '#f4f4f4',
border: '1px solid #ccc',
cursor: 'grab',
}}
>
{task.content}
</div>
));
};
return (
<div style={{ display: 'flex', gap: '20px', padding: '20px' }}>
<div
onDragOver={onDragOver}
onDrop={(e) => onDrop(e, 'todo')}
style={{ width: '200px', minHeight: '300px', background: '#ebedf0', padding: '10px' }}
>
<h3>To Do</h3>
{renderTasks('todo')}
</div>
<div
onDragOver={onDragOver}
onDrop={(e) => onDrop(e, 'completed')}
style={{ width: '200px', minHeight: '300px', background: '#ebedf0', padding: '10px' }}
>
<h3>Completed</h3>
{renderTasks('completed')}
</div>
</div>
);
};
export default TaskBoard;I executed the above example code and added the screenshot below.

I find this method works best when you want to avoid “package bloat.” However, it requires a bit of manual state management and doesn’t handle animations automatically.
Method 2: Use React DnD for Complex Workflows
When I’m working on enterprise-grade apps for US logistics or financial firms, I prefer react-dnd. It is highly performant because it uses “monitors” to track state changes.
It is a bit more complex to set up, but it gives you total control over the drag source and the drop target.
To use this, you’ll need to install: npm install react-dnd react-dnd-html5-backend.
import React from 'react';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
const ItemType = {
PRODUCT: 'product',
};
const DraggableProduct = ({ name, id }) => {
const [{ isDragging }, drag] = useDrag(() => ({
type: ItemType.PRODUCT,
item: { id, name },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
}));
return (
<div
ref={drag}
style={{
opacity: isDragging ? 0.5 : 1,
padding: '15px',
border: '1px solid black',
marginBottom: '10px',
backgroundColor: 'white',
cursor: 'move',
}}
>
{name}
</div>
);
};
const ShoppingCart = () => {
const [cart, setCart] = React.useState([]);
const [{ isOver }, drop] = useDrop(() => ({
accept: ItemType.PRODUCT,
drop: (item) => addToCart(item),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
const addToCart = (item) => {
setCart((prev) => [...prev, item]);
};
return (
<div style={{ display: 'flex', justifyContent: 'space-around', marginTop: '50px' }}>
<div>
<h2>Inventory (Chicago Warehouse)</h2>
<DraggableProduct id="1" name="MacBook Pro" />
<DraggableProduct id="2" name="iPhone 15" />
<DraggableProduct id="3" name="Dell Monitor" />
</div>
<div
ref={drop}
style={{
width: '300px',
height: '400px',
backgroundColor: isOver ? '#d1ffd1' : '#f0f0f0',
border: '2px dashed #333',
padding: '10px',
}}
>
<h2>Shopping Cart</h2>
{cart.map((item, idx) => (
<p key={idx}>{item.name}</p>
))}
</div>
</div>
);
};
const App = () => (
<DndProvider backend={HTML5Backend}>
<ShoppingCart />
</DndProvider>
);
export default App;I executed the above example code and added the screenshot below.

This method is my “go-to” for complex interfaces where multiple components need to talk to each other during a drag event.
Method 3: Use @hello-pangea/dnd for Beautiful Lists
If you’ve heard of react-beautiful-dnd, you know it was the gold standard for vertical lists. Since it’s no longer actively maintained by Atlassian, the community has moved to @hello-pangea/dnd.
I use this for “Tier List” makers or reordering team members in a US-based HR portal. It provides beautiful animations out of the box.
First, install it: npm install @hello-pangea/dnd.
import React, { useState } from 'react';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
const initialStates = [
{ id: '1', name: 'California' },
{ id: '2', name: 'Texas' },
{ id: '3', name: 'Florida' },
{ id: '4', name: 'New York' },
];
const StateRanker = () => {
const [items, setItems] = useState(initialStates);
const handleOnDragEnd = (result) => {
if (!result.destination) return;
const newItems = Array.from(items);
const [reorderedItem] = newItems.splice(result.source.index, 1);
newItems.splice(result.destination.index, 0, reorderedItem);
setItems(newItems);
};
return (
<div style={{ maxWidth: '400px', margin: 'auto' }}>
<h2>Rank US States by Population</h2>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="states">
{(provided) => (
<ul {...provided.droppableProps} ref={provided.innerRef} style={{ listStyle: 'none', padding: 0 }}>
{items.map(({ id, name }, index) => (
<Draggable key={id} draggableId={id} index={index}>
{(provided) => (
<li
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
userSelect: 'none',
padding: '16px',
margin: '0 0 8px 0',
backgroundColor: '#fff',
border: '1px solid #ddd',
borderRadius: '4px',
...provided.draggableProps.style,
}}
>
{name}
</li>
)}
</Draggable>
))}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
</div>
);
};
export default StateRanker;I executed the above example code and added the screenshot below.

The benefit here is the “physics” feel. Items slide out of the way smoothly, which makes your app feel professional and polished.
Which Method Should You Choose?
In my experience, you should choose the method based on your specific use case.
If you just need to drop a file into a box, stick with the Native HTML5 API.
If you are building a complex dashboard with many drop zones, go with react-dnd.
If you are building a simple reorderable list, @hello-pangea/dnd is the clear winner for its ease of use and animations.
You may read:
- How to Modularize React Components
- How to Disable React Strict Mode for One Component
- React PureComponent vs Component
- How to Add ClassName to React Components

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.