React Drag and Drop Component

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.

React Drag and Drop Component

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.

Drag and Drop Component in React

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.

Drag and Drop React Component

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:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.