Simple Drag and Drop List in React JS

In this article, we will learn how to build a simple Drag and Drop list using React JS with code. The Drag and Drop list will have the following functionalities:

  1. Display all the items of the list.
  2. Allow users to Drag each item.
  3. Allow users to Drop the item within HTML container.
  4. Update order of items based on drag and drop.
drag drop list React
Drag and Drop List built using React JS

1. Install React DnD npm package

First, we will install the React DnD npm package in our React project. The built-in features of the package allow us to simplify drag and drop functionality. Learn more about the React DnD from npmjs.com/package/react-beautiful-dnd.

npm i react-beautiful-dnd

// For projects configured with yarn 
yarn add react-beautiful-dnd

Once the package is installed, we can verify the dependencies in package.json of the React project.

package.json
Dependencies in package.json

2. Declare React state for item list

We will declare a JS constant “defaultList” with an array of item values in default order. Next, “itemList” React State is declared with defaultList” as default value. Every time “setItemList” is called, the value of “itemList” is updated in both screen and codebase.

const defaultList = ["A", "B", "C", "D", "E"];

// React state to track order of items
const [itemList, setItemList] = useState(defaultList);

3. JS function to perform drop action

The drop functionality is implemented in the following steps:

  1. Ignore cases where the element is dropped outside the container.
  2. Declare variable with copy of the state.
  3. Remove Dragged item from list using Array.splice() .
  4. Insert item into destination index of the array.
  5. Update the list using setItemList().
// Function to update list on drop
const handleDrop = (droppedItem) => {
  // Ignore drop outside droppable container
  if (!droppedItem.destination) return;
  var updatedList = [...itemList];
  // Remove dragged item
  const [reorderedItem] = updatedList.splice(droppedItem.source.index, 1);
  // Add dropped item
  updatedList.splice(droppedItem.destination.index, 0, reorderedItem);
  // Update State
  setItemList(updatedList);
};

4. Define DragDropContext and Droppable

To enable drag and drop functionality, the code needs to be enclosed in <DragDropContext/>. The HTML elements enclosed in <Droppable/> allows Dragged elements to be dropped.

The “onDragEnd={handleDrop}” ensures “handleDrop” function is triggered everytime dragged element is dropped.

<div className="App">
  <DragDropContext onDragEnd={handleDrop}>
    <Droppable droppableId="list-container">
    </Droppable>
  </DragDropContext>
</div>

5. Add <div></div> for list container and placeholder

Next, we add <div></div> with className as “list-container”. The “{…provided.droppableProps}” code ensures that HTML element behaves like droppable container and “ref={provided.innerRef}” adds reference to the HTML element.

<Droppable droppableId="list-container">
  {(provided) => (
    <div
     className="list-container"
     {...provided.droppableProps}
     ref={provided.innerRef}
    >
    </div>
  )}
</Droppable>

6. Create a Draggable element for every item

Further, we will create <Draggable/> element for every item, and “{provided.placeholder}” ensures constant container width during drag and drop operation.

<div
 className="list-container"
 {...provided.droppableProps}
 ref={provided.innerRef}
>
 {itemList.map((item, index) => (
   <Draggable key={item} draggableId={item} index={index}>
   </Draggable>
 ))}
 {provided.placeholder}
</div>
Q: Troubleshooting Error: "Unable to find draggable with id: ..."?
  1. Remove <StrictMode> </StrictMode> wrapper in index.js file.
  2. Ensure that the "key" prop for every <Draggable> component is assigned correctly.

7. Display every item with props

Finally, the item value is render enclosed by <div></div> with className “item-container”. The code {…provided.dragHandleProps} and {…provided.draggableProps} enables draggable behaviour for <div></div> container along with “ref={provided.innerRef}” to provide the reference.

{itemList.map((item, index) => (
  <Draggable key={item} draggableId={item} index={index}>
    {(provided) => (
      <div
       className="item-container"
       ref={provided.innerRef}
       {...provided.dragHandleProps}
       {...provided.draggableProps}
      >
        {item}
      </div>
    )}
  </Draggable>
))}

Final Solution Code

App.js

import React, { useState } from "react";
import "./styles.css";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";

export default function App() {

  const defaultList = ["A", "B", "C", "D", "E"];
  // React state to track order of items
  const [itemList, setItemList] = useState(defaultList);

  // Function to update list on drop
  const handleDrop = (droppedItem) => {
    // Ignore drop outside droppable container
    if (!droppedItem.destination) return;
    var updatedList = [...itemList];
    // Remove dragged item
    const [reorderedItem] = updatedList.splice(droppedItem.source.index, 1);
    // Add dropped item
    updatedList.splice(droppedItem.destination.index, 0, reorderedItem);
    // Update State
    setItemList(updatedList);
  };

  return (
    <div className="App">
      <DragDropContext onDragEnd={handleDrop}>
        <Droppable droppableId="list-container">
          {(provided) => (
            <div
              className="list-container"
              {...provided.droppableProps}
              ref={provided.innerRef}
            >
              {itemList.map((item, index) => (
                <Draggable key={item} draggableId={item} index={index}>
                  {(provided) => (
                    <div
                      className="item-container"
                      ref={provided.innerRef}
                      {...provided.dragHandleProps}
                      {...provided.draggableProps}
                    >
                      {item}
                    </div>
                  )}
                </Draggable>
              ))}
              {provided.placeholder}
            </div>
          )}
        </Droppable>
      </DragDropContext>
    </div>
  );
}

styles.css

.App {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
}
.list-container {
  display: flex;
  font-size: 18px;
  background-color: #eee;
  flex-direction: column;
}
.item-container {
  background-color: #fff;
  border: 1px solid black;
  padding: 25px 70px;
  margin: 15px 50px;
}