How to Implement Drag and Drop Feature for Your React Component
Jan 29, 2019 • 7 Minute Read
Introduction
One of the common features used when building a web application is to implement the drag and drop feature to make web pages look more interactive. There are many libraries which can be used to achieve this drag-and-drop feature. However, in this guide, we are not going to make use of any library and instead achieve the functionality using only the built-in features. Below are the steps that we need to perform.
Create a Basic React Component
First, we'll create a basic React component called ToDoDragDropDemo which will be our component that will implement the drag-and-drop feature. We'll define the render method which, for now, will just return a div and a child header. This would be the basic boilerplate code for our application:
import React, { Component } from 'react';
import './App.css';
export default class ToDoDragDropDemo extends Component {
render() {
return (
<div className="drag-container">
<h2 className="head">To Do List Drag & Drop</h2>
</div>
);
}
}
Define Our CSS
Next, we'll define our basic CSS for the app, container, and the header element.
.App {
width: 100%;
margin: 0 auto;
}
.drag-container {
text-align: center;
}
.head {
display: inline-block;
margin: 0;
padding: 0;
background-color: #CCCCCC;
width:100%;
}
Define Our State Object
Let’s create our "state" object which will have our list of tasks. This will be divided into two types to represent the two ToDo boxes we’ll have on our page - InProgress and Done. We’ll then want to drag and drop these individual tasks into the two types.
state = {
tasks: [
{id: "1", taskName:"Read book",type:"inProgress", backgroundColor: "red"},
{id: "2", taskName:"Pay bills", type:"inProgress", backgroundColor:"green"},
{id: "3", taskName:"Go to the gym", type:"Done", backgroundColor:"blue"},
{id: "4", taskName:"Play baseball", type:"Done", backgroundColor:"green"}
]
}
Transform the Tasks State Object into Grouped Array Elements
Next, we'll define a "tasks" object in our render method which will, essentially, have the grouping of tasks based on their type (i.e. inProgress and Done). We'll iterate through our "tasks" state object and create a "div" element representing each of the individual tasks. We'll then create headers with the two boxes, as well as render both types of tasks within their containers.
render() {
var tasks = {
inProgress: [],
Done: []
}
this.state.tasks.forEach ((task) => {
tasks[task.type].push(
<div key={task.taskName}
className="draggable"
style = {{backgroundColor: task.bgcolor}}>
{task.taskName}
</div>
);
});
return (
<div className="drag-container">
<h2 className="head">To Do List Drag & Drop</h2>
<div className="inProgress">
<span className="group-header">In Progress</span>
{tasks.inProgress}
</div>
<div className="droppable">
<span className="group-header">Done</span>
{tasks.Done}
</div>
</div>
);
}
Make the Individual Div "Draggable"
In this step, we'll see most of the action taking place. First, we'll make each of the div elements that we created above draggable by adding the "draggable" attribute. We'll also have to define the "onDragOver" event handler for the container div. This is needed to allow a drop to take place. As we drag the element, we need to keep the state of the element being dragged somewhere in the memory. So, we'll define "onDragStart" event handler for the individual div elements and we can pass an ID or any other information which we would want to be stored during the drag/drop. In the handler for the onDragStart event, we can call the setData method to store our custom attribute. Here, we store the element being dragged (i.e. taskName) into the "taskName" attribute. We can retrieve this same information whenever a drop event occurs by calling event.dataTransfer.getData().
So, let’s define the "onDrop" event handler on our container div and we'll pass the status as well (e.g. "done"). In the "onDrop" handler, we'll iterate through our "tasks" state array and we'll update the "type" attribute of the matched task with whatever parameter we passed to the onDrop handler. We'll also update our current state with the new state by invoking the setState() method, which will trigger a render.
onDragStart = (event, taskName) => {
console.log('dragstart on div: ', taskName);
event.dataTransfer.setData("taskName", taskName);
}
onDragOver = (event) => {
event.preventDefault();
}
onDrop = (event, cat) => {
let taskName = event.dataTransfer.getData("taskName");
let tasks = this.state.tasks.filter((task) => {
if (task.taskName == taskName) {
task.type = cat;
}
return task;
});
this.setState({
...this.state,
tasks
});
}
render() {
var tasks = {
inProgress: [],
Done: []
}
this.state.tasks.forEach ((task) => {
tasks[task.type].push(
<div key={task.id}
onDragStart = {(event) => this.onDragStart(event, task.taskName)}
draggable
className="draggable"
style = {{backgroundColor: task.bgcolor}}>
{task.taskName}
</div>
);
});
return (
<div className="drag-container">
<h2 className="head">To Do List Drag & Drop</h2>
<div className="inProgress"
onDragOver={(event)=>this.onDragOver(event)}
onDrop={(event)=>{this.onDrop(event, "inProgress")}}>
<span className="group-header">In Progress</span>
{tasks.inProgress}
</div>
<div className="droppable"
onDragOver={(event)=>this.onDragOver(event)}
onDrop={(event)=>this.onDrop(event, "Done")}>
<span className="group-header">Done</span>
{tasks.Done}
</div>
</div>
);
}
Define Styles for Our Draggable/Droppable and In-progress Div
.droppable {
position: absolute;
width: 200px;
height: 200px;
right: 0;
top: 10;
background-color: #9E9E9E;
border-left: 2px dotted red;
}
.draggable {
width: 100px;
height: 100px;
background-color: yellow;
margin: 10px auto;
}
.inProgress {
position: absolute;
width: 200px;
height: 200px;
left: 0;
top: 10;
background-color: #EEEEEE;
border-right: 2px dotted red;
}
Reverse Drag/Drop
Finally, we'll also make reverse drag/drop available (i.e. from Done to inProgress). For this, we'll define onDragOver and onDrop handlers for the "inProgress" div container.
<div className="inProgress"
onDragOver={(event)=>this.onDragOver(event)}
onDrop={(event)=>{this.onDrop(event, "inProgress")}}>
<span className="group-header">In Progress</span>
{tasks.inProgress}
</div>
Conclusion
You can easily customize this for your application without having to rely on any external library.