Drag and drop is a user interface (UI) interaction technique that allows users to move or manipulate digital objects (such as files, images, or elements on a webpage) by clicking and dragging them from one index and dropping them onto another. This technique simplifies the process of rearranging, copying, or transferring objects within an application or between different applications or windows.
Html has inbuilt event handlers such as :
. onDragStart
. onDragEnter
. onDragEnd
. ononline
. onpagehide
. onresize etc (checkout this like for more html event handlers https://www.w3schools.com/tags/ref_eventattributes.asp).
Which will be a helping factor in this drag-and-drop functionality.
In this project, we will be making use of very few of the html event handler such as : onDragStart, onDragEnter, and onDragEnd
Code Set-Up
Create react app
Create a folder called Component
Create another folder called data
Inside Component folder, Create a new file called resorter.js
Inside data folder, create a new file called data.js
In app.js, import resorter.js
After everything, your folder will look like this:
In the data.js, create a few data there.
In the resorter.js, create a state where our data will be stored.
const [list, setList] = useState([])
Now is time to update our list array with the data we have created using useEffect
Note: Our list will be set to local storage at the initial fetch. Any changes made to the list will also be saved to the local storage, that way our list will remain the same even after refreshing the browser.
useEffect(() => {
const listInStorage = localStorage.getItem('list')
? JSON.parse(localStorage.getItem('list'))
: [];
if (listInStorage && listInStorage.length > 0) {
setList(listInStorage);
} else {
setList(users);
}
}, []);
In the return part of the resorter.js, map the data. In my case, I have another sub-component (Item) inside resorter.js where all the data values are passed on.
const dragStartIndex = useRef(null);
const dragEndIndex = useRef(null);
Create two useRef, say dragStartIndex, dragEndIndex as shown in the code snippet above, pass it to the Item Component. These two refs will be passed to onDragStart
and onDragEnd
Html event handler. Note: You must enable item dragging by passing draggable: true
to the parent JSX tag
Other things that will be passed down to Item components are :
Index
setList (function that updates our list array)
item (The exact item that you are dragging)
list (the data array)
<ol style={{ listStyle: 'inside' }}>
{list.map((item, index) => {
return (
<Item
key={item.age + item.name}
item={item}
list={list}
setList={setList}
index={index}
dragStartIndex={dragStartIndex}
dragEndIndex={dragEndIndex}
/>
);
})}
</ol>
In the Item component, first, create a function called handleDrag ,
passing setList
and list
as parameters. Create a variable called _list
and spread the list inside array like this :
const _list = [...list];
Still within the handleDrag
function, create two variables called dragStartItem
and DragEndItem
.DragStartItem returns the item we are dragging while dragendItem returns the item we want to replace.
const dragStartItem = _list.find((x, i) => i === dragStartIndex.current);
const dragEndItem = _list.find((x, i) => i === dragEndIndex.current);
Now that we are getting the index of dragging item, index of item where the dragged item will be dropped,full details of the dragging item (stored in dragStartItem) and details of item that the dragged item will be replacing (stored in dragEndItem), we can easily manipulate our list array with splice like this:
_list.splice(dragStartIndex.current, 1, dragEndItem);
_list.splice(dragEndIndex.current, 1, dragStartItem);
After that, pass our new list(_list) to the setList function that updates our list array setList(_list).
OPTIONAL: If you want your data arrangement to remain the way you arranged them even after refreshing the browser, set the _list to local storage like this :
localStorage.setItem('list', JSON.stringify(_list));
Now that our handleDrag function is set, let's go down to the JSX part of the Item component and pass our function.
<li
style={{
borderBottom: '1px solid gray',
borderRight: '1px solid gray',
borderLeft: '1px solid gray',
display: 'flex',
gap: '20px',
alignItems: 'center',
padding: '10px',
}}
draggable={true}
//
onDragStart={(e) => (dragStartIndex.current = index)}
onDragEnter={(e) => (dragEndIndex.current = index)}
onDragEnd={(e) =>
handleDrag({
setList,
list,
})
}
>
<div
style={{
width: '50px',
height: '50px',
border: '1px solid gray',
borderRadius: '50%',
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
fontSize: '20px',
fontWeight: '500',
backgroundColor: 'gray',
color: 'white',
}}
>
{item.name.slice(0, 2)}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '5px' }}>
<p>{item.name}</p>
<div style={{ display: 'flex', gap: '5px' }}>
<span style={{ borderRight: '1px solid gray' }}>{item.gender}</span>
<span>{item.age}</span>
</div>
</div>
</li>
That's all for drag and drop functionality.
import React, { useEffect, useRef, useState } from 'react';
import { users } from '../data';
const moveToTop = ({ list = [], itemIndex, item, setList }) => {
const _list = [...list];
_list.splice(itemIndex, 1);
const newList = [item, ..._list];
setList(newList);
localStorage.setItem('list', JSON.stringify(newList));
};
const Resorter = () => {
const [list, setList] = useState([]);
const dragStartIndex = useRef(null);
const dragEndIndex = useRef(null);
useEffect(() => {
const listInStorage = localStorage.getItem('list')
? JSON.parse(localStorage.getItem('list'))
: [];
if (listInStorage && listInStorage.length > 0) {
setList(listInStorage);
} else {
setList(users);
}
}, []);
return (
<div>
<h1>User List</h1>
<div style={{ width: '100%', maxWidth: '300px' }}>
<ol style={{ listStyle: 'inside' }}>
{list.map((item, index) => {
return (
<Item
key={item.age + item.name}
item={item}
list={list}
setList={setList}
index={index}
dragStartIndex={dragStartIndex}
dragEndIndex={dragEndIndex}
/>
);
})}
</ol>
</div>
</div>
);
};
export default Resorter;
const Item = ({ item, list, setList, index, dragEndIndex, dragStartIndex }) => {
const handleDrag = ({ setList, list }) => {
const _list = [...list];
// console.log({ dragStartIndex, dragEndIndex });
const dragStartItem = _list.find((x, i) => i === dragStartIndex.current);
const dragEndItem = _list.find((x, i) => i === dragEndIndex.current);
_list.splice(dragStartIndex.current, 1, dragEndItem);
_list.splice(dragEndIndex.current, 1, dragStartItem);
// console.log({ dragStartItem, dragEndItem });
setList(_list);
localStorage.setItem('list', JSON.stringify(_list));
};
return (
<li
style={{
borderBottom: '1px solid gray',
borderRight: '1px solid gray',
borderLeft: '1px solid gray',
display: 'flex',
gap: '20px',
alignItems: 'center',
padding: '10px',
}}
draggable={true}
//
onDragStart={(e) => (dragStartIndex.current = index)}
onDragEnter={(e) => (dragEndIndex.current = index)}
onDragEnd={(e) =>
handleDrag({
setList,
list,
})
}
>
<div
style={{
width: '50px',
height: '50px',
border: '1px solid gray',
borderRadius: '50%',
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
fontSize: '20px',
fontWeight: '500',
backgroundColor: 'gray',
color: 'white',
}}
>
{item.name.slice(0, 2)}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '5px' }}>
<p>{item.name}</p>
<div style={{ display: 'flex', gap: '5px' }}>
<span style={{ borderRight: '1px solid gray' }}>{item.gender}</span>
<span>{item.age}</span>
</div>
</div>
</li>
);
};