DEV Community

Cover image for Drag and Drop starter project with ReactJS - Part 1
Vedant  Nandoskar
Vedant Nandoskar

Posted on

Drag and Drop starter project with ReactJS - Part 1

 

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
 

  1. Drag and drop components
  2. Rearrange dropped components
  3. 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
Enter fullscreen mode Exit fullscreen mode

 
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
Enter fullscreen mode Exit fullscreen mode

 
Go to the browser at http://localhost:3000 and you should see the following page
image
 
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>
...
Enter fullscreen mode Exit fullscreen mode

 
To
 

//App.js
...
    <div className="App">

    </div>
...
Enter fullscreen mode Exit fullscreen mode

 
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;

Enter fullscreen mode Exit fullscreen mode

 
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;
}
Enter fullscreen mode Exit fullscreen mode

 
This should result in the following screen
image

 

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>
...
Enter fullscreen mode Exit fullscreen mode

 
Alright, you should be able to drag your div now
image
 

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 dataTransferlearn 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);
  }
...
Enter fullscreen mode Exit fullscreen mode

 
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>
...
Enter fullscreen mode Exit fullscreen mode

 
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);
  }
...
Enter fullscreen mode Exit fullscreen mode

 
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>
...
Enter fullscreen mode Exit fullscreen mode

 
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
image
 
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: {},
    };
  }
...
Enter fullscreen mode Exit fullscreen mode

 
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
    });
  }
...
Enter fullscreen mode Exit fullscreen mode

 
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 };
Enter fullscreen mode Exit fullscreen mode

 
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';
...
Enter fullscreen mode Exit fullscreen mode

 
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>
    );
  }
...
Enter fullscreen mode Exit fullscreen mode

 
When you open the app on the browser, you should be able to drag and drop components on the Drop Area
image

 
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

  1. Make the components re-arrangeable
  2. 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

  1. Cover image taken from Unsplash @cleovermij
  2. References from Mozilla Developer Network Web Documentation

Top comments (0)