DEV Community

Cover image for Drag and Drop with React (with no libraries) revised
Aaron Wolf
Aaron Wolf

Posted on • Updated on

Drag and Drop with React (with no libraries) revised

Intro

In this tutorial I will show how to make a Drag and Drop component from scratch with no 3rd party libraries using React.

I already made a post on Drag and Drop in React, so why make a new one?

Well, many of us learn new things and think we have a good understanding of it. But then we learn more and realize that there was more to know. So I made a new Drag and Drop that uses better methodologies. Enjoy.

Important info

  1. There exists the event handler e.dataTransfer which helps with drag and drop functionality, but as we're using React I find using state to be simpler.

  2. Make sure to check out the Code Sandbox. I may add a few things that aren't reflected below, but the code below is complete.

  3. You might know a better way to do this! If you think you can improve the code please comment.

Drag and Drop in HTML5

There are a few new elements that we will be using thought we're not using all of the HTML5 Drag and Drop elements.

  1. draggable makes a div draggable (instead of highlighting when you click and drag)
  2. onDragStart fires ONCE when you begin to drag
  3. onDragEnter fires ONCE when the dragged div enters another.
  4. onDragOver fires CONTINUOUSLY when dragging over a div
  5. onDrop fires when the mouse click is released

The final 4 of these we will pass to JavaScript to give it the DND logic.

Getting Started.

Let's make a some groups to drag between and some items to be dragged around.

Dnd.js



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

export default function Dnd() {

  // my groups to be dragged between

  const groups = ["group1", "group2", "group3", "noDrop"];

  // My items to be dragged around

  const initialItems = [
    { id: 1, group: "group1", value: "drag 1" },
    { id: 2, group: "group1", value: "drag 2" },
    { id: 3, group: "group1", value: "drag 3" }
  ];

  return (
    <>

      // Creating the group divs 

      <div className="groups">
        {groups.map((group) => (
          <div className="group">
            <h1 className="title">{group}</h1>
            <div>

              // Creating our items to drag and drop

              {items
                .filter((item) => item.group === group)
                .map((item) => (
                  <div
                    key={item.id}
                    id={item.id}
                    className="item"

                    // THIS MAKES THE ITEM DRAGGABLE!!!

                    draggable
                  >
                     // item title
                    {item.value}
                  </div>
                ))}
            </div>
          </div>
        ))}
      </div>
    </>
  );
}



Enter fullscreen mode Exit fullscreen mode

Dnd.scss



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


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

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


.item {
  background-color: yellow;
  color: blue;
  margin: 5px;
  padding: 5px;
  border: 2px green;
  cursor: grab;
}


Enter fullscreen mode Exit fullscreen mode

This creates something that looks like this:
Image description

Now we'll add the events and the event handlers. Make sure to read the comments in the code as that's where the explanations are. I think this is simpler than describing everything outside of the code.

Hint: The comments are easier to read in the Code Sandbox.

Dnd.js



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

export default function Dnd() {
  // Initial groups to drag between
  const groups = ["group1", "group2", "group3", "noDrop"];
  // Initial items to be dragged 
  const initialItems = [
    { id: 1, group: "group1", value: "drag 1" },
    { id: 2, group: "group1", value: "drag 2" },
    { id: 3, group: "group1", value: "drag 3" }
  ];
  // Sets the state of the items. I may add an "add" function later
  // Can be used to add items
  const [items, setItems] = useState(initialItems);
  // Data about a thing's id, origin, and destination
  const [dragData, setDragData] = useState({});
  // Are we hovering over the noDrop div?
  const [noDrop, setNoDrop] = useState("");

  // onDragStart we setDragData.
  // useState instead of e.dataTransfer so we can transfer more data
  const handleDragStart = (e, id, group) => {
    setDragData({ id: id, initialGroup: group });
  };

  // If we enter the noDrop zone the state will be updated
  // Used for styling.
  const handleDragEnter = (e, group) => {
    if (group === "noDrop") {
      setNoDrop("noDrop");
    }
  };

  // DND will not work without this.
  const handleDragOver = (e) => {
    e.preventDefault();
  };

  // setNoDrop to nothing to return styling to normal
  const handleDragLeave = (e) => {
    setNoDrop("");
  };

  // 1. makes copy of items (newItems)
  // 2. changes category of the item to its new group
  // 3. setItem to our NewItems
  const changeCategory = (itemId, group) => {
    const newItems = [...items];
    newItems[itemId - 1].group = group;
    setItems([...newItems]);
  };

  // 1. setNoDrop in case item was dropped in noDrop
  // 2. gets the item id
  // 3. doesn't allow drop in noDrop
  // 4. changeCategory (see above)
  const handleDrop = (e, group) => {
    setNoDrop("");
    const selected = dragData.id;
    if (group === "noDrop") {
      console.log("nuh uh");
    } else {
      changeCategory(selected, group);
    }
  };

  return (
    <>
      <div className="groups">
        {/* iterate over groups */}
        {groups.map((group) => (
          <div
            // change styling if dragging into noDrop zone
            className={`${
              group === "noDrop" && noDrop === "noDrop" ? noDrop : "group"
            }`}
            // event handlers
            onDragEnter={(e) => handleDragEnter(e, group)}
            onDragOver={handleDragOver}
            onDragLeave={handleDragLeave}
            onDrop={(e) => handleDrop(e, group)}
            key={group}
          >
            <h1 className="title">{group}</h1>
            <div>
              {/* iterate over items */}
              {items
                .filter((item) => item.group === group)
                .map((item) => (
                  <div
                    key={item.id}
                    id={item.id}
                    // change style if dragged over noDrop
                    className={`${
                      group === "noDrop" && noDrop === "noDrop"
                        ? "notAllowed"
                        : "item"
                    }`}
                    // MAKES THE ITEM DRAGGABLE!!!!
                    draggable
                    // event handler
                    onDragStart={(e) => handleDragStart(e, item.id, group)}
                  >
                    {/* The name of each item */}
                    {item.value}
                  </div>
                ))}
            </div>
          </div>
        ))}
      </div>
    </>
  );
}


Enter fullscreen mode Exit fullscreen mode

Dnd.scss



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

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

<span class="nc">.title</span><span class="p">{</span>
  <span class="nl">color</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">margin-top</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>
Enter fullscreen mode Exit fullscreen mode

}
.noDrop {
margin: 2px;
padding: 20px;
min-height: 16rem;
background-color: red;
cursor: not-allowed !important;

<span class="nc">.title</span><span class="p">{</span>
  <span class="nl">color</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">margin-top</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>
Enter fullscreen mode Exit fullscreen mode

}
}

.item {
background-color: yellow;
color: blue;
margin: 5px;
padding: 5px;
border: 2px green;
cursor: grab;
}

.notAllowed {
background-color: yellow;
color: blue;
margin: 5px;
padding: 5px;
border: 2px green;
cursor: not-allowed;
}

Enter fullscreen mode Exit fullscreen mode




This is what it looks like

Image description

Conclusion

This is the basic gist of it. If you need something simple that works this is it, otherwise feel free to install a library.

Watch it in action! See the Code Sandbox

Top comments (3)

Collapse
 
biomathcode profile image
Pratik sharma

Great article. Tip you can use ```javascript for syntax highlighting !!!

Collapse
 
wolfmath profile image
Aaron Wolf

Updated! Thanks for the tip.

Collapse
 
coderamrin profile image
Amrin

amazing Aaron, Thanks for sharing