Author avatar

Chris Parker

How to Implement Drag and Drop Feature for Your React Component

Chris Parker

  • Jan 29, 2019
  • 7 Min read
  • 2,402 Views
  • Jan 29, 2019
  • 7 Min read
  • 2,402 Views
React

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:

1
2
3
4
5
6
7
8
9
10
11
12
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>
	    );
  }
}
javascript

Define Our CSS

Next, we'll define our basic CSS for the app, container, and the header element.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.App {
  width: 100%;
  margin: 0 auto;
}

.drag-container {
  text-align: center;
}

.head {
  display: inline-block;
  margin: 0;
  padding: 0;
  background-color: #CCCCCC;
  width:100%;
}
css

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.

1
2
3
4
5
6
7
8
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"}
	]
}
javascript

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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>
	    );
  	}
javascript

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
	
	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>
	    );
  	}
javascript

Define Styles for Our Draggable/Droppable and In-progress Div

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.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;
}
css

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.

1
2
3
4
5
6
<div className="inProgress"
	onDragOver={(event)=>this.onDragOver(event)}
	onDrop={(event)=>{this.onDrop(event, "inProgress")}}>
  	<span className="group-header">In Progress</span>
  	{tasks.inProgress}
</div>
javascript

Conclusion

You can easily customize this for your application without having to rely on any external library.

19