Drag and Drop Interface
Implement a basic drag and drop interface where users can move items between different containers.
Solution
Explanation
JS Drag & Drop API
The solution mimics the pattern for vanilla JavaScript with the exception that it uses state
and refs
to keep track of the elements.
The HTML structure
We have 2 main div containers, the first of which has two items. The 2 parent containers have the properties onDrop
and onDragOver
and a ref
to make sure we can access which dropZone
we are targetting.
The items currently placed in the first container have the properties draggable
and onDragStart
. draggable
tells the API that the element can be dragged. onDragStart
allows us to know which element is being dragged.
dragStartHandler
When the draggable item begins to be moved, this triggers a drag event
. The dragStartHandler
function takes the current property of the ref and tells us which item is being dragged. This is stored in state
as draggedItem
.
dragOverHandler
This function is present to allow dragging and provide the animation.
dropHandler
Now to drop the item, the dropHandler
function establishes if we are over a box by looking for the box
style. Then if that is true and we have a draggedItem
(which we do), then it will append the draggedItem to the box.
Code
import { useRef, useState } from "react";
import styles from "./Solution.module.css";
const Solution = () => {
const dragItem1 = useRef<HTMLDivElement>(null);
const dragItem2 = useRef<HTMLDivElement>(null);
const dropZone1 = useRef<HTMLDivElement>(null);
const dropZone2 = useRef<HTMLDivElement>(null);
const [draggedItem, setDraggedItem] = useState<HTMLDivElement | null>(null);
const dragStartHandler = (event: React.DragEvent<HTMLDivElement>) => {
// Set the dragged item based on which element initiated the drag
if (event.target === dragItem1.current) {
setDraggedItem(dragItem1.current);
} else if (event.target === dragItem2.current) {
setDraggedItem(dragItem2.current);
}
};
const dropHandler = (event: React.DragEvent<HTMLDivElement>) => {
// required to allow dragging
event.preventDefault();
// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
const dropZone = (event.target as HTMLElement).closest(`.${styles.box/}`); // Ensures we're dropping into a box
if (dropZone && draggedItem) {
dropZone.appendChild(draggedItem);
}
};
const dragOverHandler = (event: React.DragEvent<HTMLDivElement>) => {
// required to allow dragging
event.preventDefault();
event.dataTransfer.dropEffect = "move";
};
return (
<>
<div className={styles.container}>
<div
className={styles.box}
onDrop={dropHandler}
onDragOver={dragOverHandler}
ref={dropZone1}
>
<div
ref={dragItem1}
draggable="true"
onDragStart={dragStartHandler}
className={`${styles.dragItem} ${styles.one}`}
>
Move Me!
</div>
<div
ref={dragItem2}
draggable="true"
onDragStart={dragStartHandler}
className={`${styles.dragItem} ${styles.two}`}
>
Move Me!
</div>
</div>
<div
className={styles.box}
onDrop={dropHandler}
onDragOver={dragOverHandler}
ref={dropZone2}
></div>
</div>
</>
);
};
export default Solution;
Styling
.container {
display: flex;
gap: 15px;
}
.box {
display: flex;
gap: 15px;
width: 300px;
height: 300px;
padding: 10px;
border: 1px solid black;
}
.dragItem {
width: 100px;
height: 100px;
display: grid;
place-content: center;
cursor: pointer;
}
.one {
background-color: rgb(246, 168, 168);
}
.two {
background-color: rgb(168, 246, 182);
}