Let’s be honest, the libraries out there suck. You have the dnd kit, which is more suitable for sorted drag-and-drop lists. React DnD is not even worth looking into. Then there’s react-beautiful-dnd, which is really only for sorted lists.
And, I know, sorted lists are amazing. They’re awesome, but for that to be the only talked about use case is just sad. I mean, use your imagination. Drag-and-drop can do much more than make stupid forms.
With that being said, do we really need one of these small-minded libraries that will only hinder us later? No. Instead, let’s add drag-and-drop functionality in our Next.js projects without sticking to our pathetic options. And, adding drag-and-drop functionality in next.js is not that hard. So, let’s get started.
Creating Draggable Component
We can start by creating our own draggable items. So, let’s make a components folder with a Draggable.tsx
file inside. In this file, we will create a draggable item. Copy and paste this code:
import { useRef, useState } from 'react';
const DraggableItem = ({ id, children, onDragStart }: { id: string; children: React.ReactNode; onDragStart: () => void }) => {
const itemRef = useRef(null);
const [dragging, setDragging] = useState(false);
const handleDragStart = (e: any) => {
e.dataTransfer.setData('text/plain', id);
e.dataTransfer.effectAllowed = 'move';
setDragging(true);
if (onDragStart) onDragStart();
};
const handleDragEnd = () => {
setDragging(false);
console.log(`Drag ended for item with id: ${id}`);
};
return (
<div
ref={itemRef}
draggable
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
style={{
cursor: 'grab',
padding: '10px',
margin: '5px',
backgroundColor: dragging ? '#e0e0e0' : '#f0f0f0',
border: '1px solid #ccc',
borderRadius: '4px',
width: '100%',
height: '100%',
}}
>
{children}
</div>
);
};
export default DraggableItem;
Creating Drop Zone
Now, we could create our drop zone. So, create a file named DropZone.tsx
inside the components folder. Inside this file, we want to do a couple of things. In this tutorial, we’ll make the drop zone a grid with zoom-in and zoom-out functionality. We’ll also make sure the draggable item can still be dragged and dropped after placing it in the drop zone. So, copy and paste this code:
import { useState, useEffect } from 'react';
import DraggableItem from './Draggable';
const DropZone = ({ onDrop }: { onDrop: (id: string) => void }) => {
const [zoomLevel, setZoomLevel] = useState(1);
const [gridSize, setGridSize] = useState(3);
const [grid, setGrid] = useState(Array(3 * 3).fill(null));
const [draggedItemIndex, setDraggedItemIndex] = useState<number | null>(null);
useEffect(() => {
console.log(`Grid size changed: ${gridSize}`);
setGrid((prevGrid) => {
const newGrid = Array(gridSize * gridSize).fill(null);
prevGrid.forEach((item, index) => {
if (item && index < newGrid.length) {
newGrid[index] = item;
}
});
return newGrid;
});
}, [gridSize]);
const handleDrop = (e: any, index: any) => {
e.preventDefault();
const id = e.dataTransfer.getData('text/plain');
const newGrid = [...grid];
if (draggedItemIndex !== null && draggedItemIndex !== index) {
newGrid[draggedItemIndex] = null;
newGrid[index] = id;
setDraggedItemIndex(null);
} else {
newGrid[index] = id;
}
setGrid(newGrid);
onDrop(id);
};
const handleDragOver = (e: any) => {
e.preventDefault();
};
const handleDragStart = (index: any) => {
setDraggedItemIndex(index);
};
const handleZoomIn = () => {
console.log("handleZoomIn");
const newSize = Math.max(1, Math.floor(gridSize / 1.1));
console.log(`New grid size on zoom in: ${newSize}`);
setZoomLevel(prevZoomLevel => prevZoomLevel + 0.1);
setGridSize(newSize);
};
const handleZoomOut = () => {
console.log("handleZoomOut");
const newSize = Math.max(1, gridSize + 1);
console.log(`New grid size on zoom out: ${newSize}`);
setZoomLevel(prevZoomLevel => prevZoomLevel - 0.1);
setGridSize(newSize);
};
return (
<div>
<div className="zoom-controls">
<button onClick={handleZoomIn}>Zoom In</button>
<button onClick={handleZoomOut}>Zoom Out</button>
</div>
<div
style={{
display: 'grid',
gridTemplateColumns: `repeat(${gridSize}, 1fr)`,
border: '2px dashed #ccc',
}}
className='w-full h-screen overflow-y-auto overflow-x-auto'
>
{grid.map((item, index) => (
<div
key={index}
onDrop={(e) => handleDrop(e, index)}
onDragOver={handleDragOver}
style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: item ? '#e0e0e0' : 'transparent',
border: '1px solid #ccc',
}}
>
{item ? (
<DraggableItem id={item} onDragStart={() => handleDragStart(index)}>
{item}
</DraggableItem>
) : null}
</div>
))}
</div>
</div>
);
};
export default DropZone;
Putting The Pieces Together
So, now that we have our components, let’s go to our src/app/page.tsx
file and display our drop zone and draggable items:
'use client';
import { useState } from 'react';
import DraggableItem from './components/Draggable';
import DropZone from './components/DropZone';
const Home = () => {
const [items, setItems] = useState(['item1', 'item2', 'item3']);
const [droppedItems, setDroppedItems] = useState<string[]>([]);
const [draggedItemIndex, setDraggedItemIndex] = useState<number | null>(null);
const handleDragStart = (index: any) => {
setDraggedItemIndex(index);
};
const handleDrop = (id: any) => {
setDroppedItems([...droppedItems, id]);
setItems(items.filter(item => item !== id));
};
return (
<main>
<h1>Drag and Drop Example</h1>
<div style={{ display: 'flex', flexDirection: 'row' }}>
{items.map((item, index) => (
<DraggableItem key={item} id={item} onDragStart={() => handleDragStart(index)}>
{item}
</DraggableItem>
))}
</div>
<DropZone onDrop={handleDrop} />
<div>
<h2>Dropped Items:</h2>
{droppedItems.map((item, index) => (
<div key={index}>{item}</div>
))}
</div>
</main>
);
};
export default Home;
That wraps up this tutorial on how to add drag-and-drop functionality in your Next.js project. Maybe I was a little bit too harsh in the introduction. If you want to go with a drag-and-drop library go for it. No one is stopping you. And I’m not saying that this tutorial will be the solution to your problems. Obviously, you would need to tweak this code a little or a lot to get it to work how you imagined. But, for me, creating my own drag-and-drop functionality for my project was a step in the right direction.
If you liked this tutorial follow me on Medium. Also, subscribe to my newsletter.
Happy Coding!
Top comments (0)