Hello fellow developers,
This will be about creating a drag and drop starter project in ReactJS. ReactJS is an open-source, JavaScript framework for building interactive user interfaces. We would be developing a Single Page Application (SPA) that would enable us to
- Drag and drop components
- Rearrange dropped components
- Remove dropped components We would be creating our own components and using some of the flexbox tricks I explained in my previous blog. By the end of this series, we would have the following app Drag and Drop Starter demo
1. First we setup a project with Create React App
Make sure you have Node (>v12) installed, if not install from here
Open a terminal and run
npx create-react-app app ## replace app by any name you like
React will build the app for you. Once its done, go to the directory and run the app
cd app ## or whatever you named it
npm start
Go to the browser at http://localhost:3000
and you should see the following page
If you can see that, our setup is good, onto the next step
2. Add in static UI elements
First, we clear everything in the src\App.css
and edit the App component in src\App.js
as follows. Note that we are also converting the Functional component to Class component because we would be using state
to handle drag and drop features. Learn more about class and functional components here.
// App.js
...
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
...
To
//App.js
...
<div className="App">
</div>
...
And remove the line import logo from './logo.svg';
from it.
Now add our UI Components to the App Component
//App.js
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div className="App">
<div className="components-list">
<div className="component blue">
Blue Component
</div>
<div className="component green">
Green Component
</div>
<div className="component purple">
Purple Component
</div>
</div>
<div className="drop-area">
Drop Area
</div>
</div>
);
}
}
export default App;
And App.css
as
/* App.css */
.App {
display: flex;
flex-wrap: nowrap;
padding: 10px 10px 0 10px;
}
.components-list {
width: 18vw;
margin-right: 2vw;
padding: 4vh 2vw 0 2vw;
height: 95vh;
}
.drop-area {
width: 80vw;
height: 95vh;
border: 1px black dashed;
border-radius: 1vw;
font-size: 64px;
text-align: center;
line-height: 95vh;
color: gray;
user-select: none;
}
.component {
cursor: pointer;
width: 12vw;
text-align: center;
font-size: 20px;
line-height: 10vh;
background: white;
}
.components-list .component {
margin-bottom: 4vh;
}
.blue {
color: blue;
border: 2px solid blue;
border-radius: 10px;
}
.green {
color: green;
border: 2px solid green;
border-radius: 10px;
}
.purple {
color: purple;
border: 2px solid purple;
border-radius: 10px;
}
This should result in the following screen
Making the drag and drop feature
Now, we make our component divs draggable. To do this, we set the draggable attribute to true
.
//App.js
...
<div className="component blue" draggable={true}>
Blue Component
</div>
<div className="component green" draggable={true}>
Green Component
</div>
<div className="component purple" draggable={true}>
Purple Component
</div>
...
Alright, you should be able to drag your div now
Next, we have to handle the onDragStart
event. The plan is to define three categories, which would be used to identify the type of div being dragged. For this, we can choose three simple names ie, 'blue' for the blue component, 'green' for the green component, and 'purple' for the purple component. We store this data in the dataTransfer
learn more attribute of the drag
event.
Create a class method in the App
component as follows,
//App.js
...
dragStart = (ev, type) => {
ev.dataTransfer.setData("type", type);
}
...
And assign that method to the onDragStart
attribute of the divs along with their respective "types"(, or categories)
//App.js
...
<div className="component blue" draggable={true} onDragStart={(ev) => { this.dragStart(ev, 'blue') }}>
Blue Component
</div>
<div className="component green" draggable={true} onDragStart={(ev) => { this.dragStart(ev, 'green') }}>
Green Component
</div>
<div className="component purple" draggable={true} onDragStart={(ev) => { this.dragStart(ev, 'purple') }}>
Purple Component
</div>
...
Now, we have to retrieve this data at the drop event on the Drop Area. For this, we create a class method that would work as the handler to handle the same.
//App.js
...
drop = (ev) => {
ev.preventDefault();
const type = ev.dataTransfer.getData("type");
console.log(type);
}
...
We then assign this function to the onDrop
attribute of the Drop Area
//App.js
...
<div className="drop-area" onDrop={this.drop} >
Drop Area
</div>
...
When you test it, you should see an output on the developer tools of your browser when you drop the div on the drop area
Okay, so everything looks good so far, but we've to still make our app 'memorize' the dropped components and their position. So we create two properties in the App component's state defined in the constructor. These properties are elementData
(used to hold data of dropped elements) and nextElementId
(to store a unique key for each dropped element).
//App.js
...
constructor(props) {
super(props);
this.state = {
nextElementId: 0,
elementData: {},
};
}
...
And we update the state when any new div is dropped in the Drop Area. So we update the drop
class method as follows,
//App.js
...
drop = (ev) => {
ev.preventDefault();
const type = ev.dataTransfer.getData("type");
const { elementData, nextElementId } = this.state;
const newElementData = {
type,
left: ev.clientX,
top: ev.clientY,
}
elementData[nextElementId] = newElementData;
ev.dataTransfer.clearData();
this.setState({
elementData,
nextElementId: nextElementId + 1, // Increment it for next element
});
}
...
Since we're updating the state, we also need to retrieve this data and render the respective elements in the DOM. To do this, we create a new file in the src
directory named utility.js
. In this file, we add the following function, and export it
//utility.js
const getElementList = (elementData) => {
const elements = [];
Object.keys(elementData).forEach(key => {
let { type, left, top } = elementData[key];
switch (type) {
case 'blue':
elements.push(
<div
className="component blue"
key={key}
style={{
position: 'absolute',
left: left + 'px',
top: top + 'px',
zIndex: 100,
}}
>
Blue Component
</div>
);
break;
case 'green':
elements.push(
<div
className="component green"
key={key}
style={{
position: 'absolute',
left: left + 'px',
top: top + 'px',
}}
>
Green Component
</div>
);
break;
case 'purple':
elements.push(
<div
className="component purple"
key={key}
style={{
position: 'absolute',
left: left + 'px',
top: top + 'px',
}}
>
Purple Component
</div>
);
break;
default:
break;
}
});
return elements;
}
export { getElementList };
What this function does is it takes an object containing data of dropped elements, and returns an array consisting of HTML elements with their data. This element can be used to render the components in the render
method of the App component.
First import the function in App.js
//App.js
...
import { getElementList } from './utility';
...
Then, we call the getElementList
in the render function. We pass the state data as a parameter to this function, and store the returned array in a variable called elements
. We then render this elements
array in the DOM inside the Drop Area div.
//App.js
...
render() {
const { elementData } = this.state;
const elements = [ ...getElementList(elementData) ];
return (
<div className="App">
<div className="components-list">
<div className="component blue" draggable={true} onDragStart={(ev) => { this.dragStart(ev, 'blue') }}>
Blue Component
</div>
<div className="component green" draggable={true} onDragStart={(ev) => { this.dragStart(ev, 'green') }}>
Green Component
</div>
<div className="component purple" draggable={true} onDragStart={(ev) => { this.dragStart(ev, 'purple') }}>
Purple Component
</div>
</div>
<div className="drop-area" onDrop={this.drop} onDragOver={this.dragOver}>
Drop Area
{ elements }
</div>
</div>
);
}
...
When you open the app on the browser, you should be able to drag and drop components on the Drop Area
Good going, we now have built a basic drag and drop feature using nothing but React. Since, this blog is getting too long, I will make it into a series.
In part two, we will
- Make the components re-arrangeable
- Make the components delete-able
Alright, thank you for being with me this far,
Peace and stay safe ✌️
You can follow me on my [Dev.to @vedant1202]
and on my Github@Vedant1202
Link to the Second Part
Footnotes
- Cover image taken from Unsplash @cleovermij
- References from Mozilla Developer Network Web Documentation
Top comments (0)