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 div
s 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>
);
}
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;
}
Together these two things create 7 yellow boxes inside 3 green boxes.
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
>
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)}
>
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)}
>
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)]);
};
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>
);
}
Top comments (0)