DEV Community

Cover image for Super Simple Drag N' Drop from Scratch in React (without dependencies)
Aaron Wolf
Aaron Wolf

Posted on • Updated on

Super Simple Drag N' Drop from Scratch in React (without dependencies)

Read the updated version of this tutorial here

I recently had been tasked to create Drag N' Drop component from scratch - so no npm i react-beautiful-dnd. Before tackling this I decided (with a helpful suggestion from a coworker) to write it in CodeSandbox first. Now I get to share it with you!

Disclaimer: This is how I approached the problem, but I have no doubts that there are other/better ways to go about doing it. If you feel there's a better way I'd appreciate you writing it in the comments. This is very rudimentary, so if you decide to use it know that you'd need to make refinements.

Set Up

First I created 3 groups to drag and drop between, then I iterated over them to create the divs that they would create. Then I created an array of 7 objects that can be dragged from one group to another. These are saved in state, hence the useState import.

import React, { useState } from "react";

export default function Dnd() {
  const groups = ["group1", "group2", "group3"];
  const [items, setitems] = useState([
    { id: 1, group: groups[0], value: "Chicken" },
    { id: 2, group: groups[0], value: "Monkey" },
    { id: 3, group: groups[0], value: "Duck" },
    { id: 4, group: groups[1], value: "Rhino" },
    { id: 5, group: groups[1], value: "Sandwich" },
    { id: 6, group: groups[2], value: "Ostrich" },
    { id: 7, group: groups[2], value: "Flamingo" }
  ]);

  return (
    <div className="groups">
      {groups.map((group) => (
        <div
          className="group"
          key={group}
        >
          <h1 className="title">{group}</h1>
          <div>
            {items
              .filter((item) => item.group === group)
              .map((thing) => (
                <div
                  key={thing.id}
                  id={thing.id}
                  className="thing"
                >
                  {thing.value}
                </div>
              ))}
          </div>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Then we have the scss that goes along with it (it's not pretty, but just enough to get the visuals right).

.groups {
  display: flex;
  margin: 20px;
  padding: 20px;
  flex-wrap: wrap;

  .group {
    margin: 20px;
    padding: 20px;
    min-height: 16rem;
    background-color: green;

    .title{
      color: white;
      padding: 0;
      margin-top: 0;
    }
  }

  .group-hover {
    margin: 20px;
    padding: 20px;
    background-color: darkgreen
  }
}

.thing {
  background-color: yellow;
  color: blue;
  margin: 5px;
  padding: 5px;
  border: 2px green;
}
Enter fullscreen mode Exit fullscreen mode

Together these two things create 7 yellow boxes inside 3 green boxes.
Image description

DND

Now we get to discuss how to start the DND functionality.

First is to set the thing (animal) div to be draggable. This will allow the click and drag of the div, but it won't actually do anything.

<div
  key={thing.id}
  id={thing.id}
  className="thing"
  draggable
>
Enter fullscreen mode Exit fullscreen mode

Next we need to have a little understanding of the HTML5 onDrag events. There are different events for the dragged item and the item being dragged over/onto.

I set up some state to know which item is being dragged at any given time then on the dragged item I set the state onDragStart and I created a function to handle the dragStart.

...

const [dragging, setDragging] = useState();

...  

const handleDragStart = (e) => {
  setDragging(e.target);
};

...

<div
  ...
  draggable
  onDragStart={(e) => handleDragStart(e)}
>
Enter fullscreen mode Exit fullscreen mode

Lastly we need to handle what happens to the dragged over group. So I just have the animal added to the group as soon as it's dragged into the new group, but you can edit that behavior later. I'm using the onDragEnter which fires once when the dragged item enters the div.

<div
  className="group"
  key={group}
  onDragEnter={(e) => handleDragEnter(e, group)}
>
Enter fullscreen mode Exit fullscreen mode

Then I created the handleDragEnter function to set the state of the dragged item to the group of that it's being dragged into.

const handleDragEnter = (e, group) => {
  setitems([...items, (items[dragging.id - 1].group = group)]);
};
Enter fullscreen mode Exit fullscreen mode

Image description

Now the whole DND should be fully functional! Here's all the code put together and the CodeSandbox code!

import React, { useState } from "react";
import "./Dnd.scss";

export default function Dnd() {
  const groups = ["group1", "group2", "group3"];
  const [items, setitems] = useState([
    { id: 1, group: groups[0], value: "Chicken" },
    { id: 2, group: groups[0], value: "Monkey" },
    { id: 3, group: groups[0], value: "Duck" },
    { id: 4, group: groups[1], value: "Rhino" },
    { id: 5, group: groups[1], value: "Sandwich" },
    { id: 6, group: groups[2], value: "Ostrich" },
    { id: 7, group: groups[2], value: "Flamingo" }
  ]);

  const [dragging, setDragging] = useState();

  const handleDragStart = (e) => {
    setDragging(e.target);
  };

  const onDragEnter = (e, group) => {
    setitems([...items, (items[dragging.id - 1].group = group)]);
  };

  return (
    <div className="groups">
      {groups.map((group) => (
        <div
          className="group"
          key={group}
          onDragEnter={(e) => onDragEnter(e, group)}
        >
          <h1 className="title">{group}</h1>
          <div>
            {items
              .filter((item) => item.group === group)
              .map((thing) => (
                <div
                  key={thing.id}
                  id={thing.id}
                  className="thing"
                  draggable
                  onDragStart={(e) => handleDragStart(e)}
                >
                  {thing.value}
                </div>
              ))}
          </div>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)