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:
- Display all the items of the list.
- Allow users to Drag each item.
- Allow users to Drop the item within HTML container.
- Update order of items based on drag and drop.
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.
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:
- Ignore cases where the element is dropped outside the container.
- Declare variable with copy of the state.
- Remove Dragged item from list using Array.splice() .
- Insert item into destination index of the array.
- 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: ..."?
- Remove <StrictMode> </StrictMode> wrapper in index.js file.
- 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;
}