Create a sortable list
Written byPhuoc Nguyen
Created
02 Dec, 2023
Tags
HTML 5 drag and drop, sortable list
A sortable list is a handy feature that allows users to rearrange items in a specific order. It's made up of a list of items and a drag-and-drop mechanism that enables the user to move items to a new position within the list. This feature can be beneficial in many scenarios where users need to prioritize or organize information, such as to-do lists, bookmarks, or product catalogs. By offering an easy and intuitive way for users to reorder items, sortable lists can enhance the usability and efficiency of various types of applications.
Sortable lists are widely used on many websites to improve their interface and make it easier for users to interact with their content. For example, Trello utilizes draggable cards to create a customized and organized workflow, while Airbnb uses a sortable list to help travelers filter search results by price, location, amenities, and other criteria.
Spotify's playlist editor also employs a drag-and-drop interface that enables users to rearrange songs in any order they want, making it easy to create custom playlists for different moods, occasions, or genres.
These are just a few examples of how sortable lists can enhance the user experience on various types of websites. By providing users with more control over how they interact with content, sortable lists can increase engagement and satisfaction with a website or app.
In this post, we'll learn how to create a sortable list with React.
#Understanding the data model
Let's simplify things by creating a sortable list with two properties for each item:
`id`
and `content`
. The `id`
property is unique and used to distinguish between items, while the `content`
property represents the actual content of each item.Here's how the sortable list renders a list of items:
tsx
const SortableList = () => {
return (
<div>
{
items.map((item) => (
<div key={item.id}>
{item.content}
</div>
))
}
</div>
);
};
The
`SortableList`
component shows an array of `items`
using the `map()`
method. For each item in the array, it makes a new `<div>`
element with a unique `key`
attribute set to the item's `id`
. The content of the item is then displayed within this element using the `item.content`
property. This creates a list that you can sort by dragging and dropping.Next, let's take the sortable list component to the next level by adding a
`children`
prop. But before that, let's explore how we can leverage HTML 5 drag and drop feature to transform it into a fully functional and easy-to-use sortable list.#Making items draggable
To make items draggable, we use an additional state called
`draggingIndex`
to keep track of which item is being dragged. We also use the `useRef()`
hook to track the corresponding item during the drag and drop operation.ts
const [draggingIndex, setDraggingIndex] = React.useState(-1);
const dragNode = React.useRef();
To handle the changes that occur when dragging and dropping items, we use an internal state to manage them.
ts
const [items, setItems] = React.useState(...);
We're making the items draggable by using the
`draggable`
attribute. Additionally, we're utilizing the `onDragStart`
and `onDragOver`
events to keep track of when we begin dragging an item and when we drag it over another item in the list.tsx
items.map((item, index) => (
<div
key={item.id}
draggable='true'
onDragStart={(e) => handleDragStart(e, index)}
onDragOver={(e) => handleDragOver(e, index)}
>
{item.content}
</div>
))
When the user starts dragging an item, it triggers the
`handleDragStart()`
function. This function takes two parameters: `e`
which represents the drag event, and `index`
which is the index of the item being dragged.To keep track of the item being dragged, we use the
`setDraggingIndex()`
function to set the current dragging index to the index of the item being dragged. We also store a reference to the DOM node being dragged in the ref using the `current`
property.To pass data about what's being dragged, we call
`e.dataTransfer.setData('...', target)`
and set the first parameter to `'text/html'`
. If you need to pass more complex data, you can set it to other MIME types like `'application/json'`
.Finally, we set
`effectAllowed`
on the drag event to `'move'`
. This tells the browser that we intend to move or copy this element during the drag operation.Here's how we handle the
`dragstart`
event for each item:ts
const handleDragStart = (e, index) => {
const { target } = e;
setDraggingIndex(index);
dragNode.current = target;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', target);
};
The
`handleDragOver()`
function gets called when you drag an item over another item in the list. It takes two arguments: `e`
and `index`
. The `e`
parameter represents the drag event, while the `index`
parameter is the index of the item being dragged over.Inside this function, we first call
`e.preventDefault()`
to prevent the default browser behavior for drag events. Then, we check if the current target of the drag event is not equal to the reference to the DOM node being dragged (`dragNode.current`
). If it's not, we proceed with updating our state.We create a new array called
`newItems`
, which is a copy of our original items array using spread syntax (`[...items]`
). We then use splice to remove one element from our newItems array at index `draggingIndex`
and insert it back into our newItems array at index `index`
.After that, we update our state by calling both setDraggingIndex() and setItems(). The former updates our draggingIndex state with the current index, while the latter updates our items state with our newly sorted newItems array.
This way, when an item is dragged over another item in the list, it swaps positions with that item, allowing users to easily reorder items in their sortable list.
ts
const handleDragOver = (e, index) => {
e.preventDefault();
if (dragNode.current !== e.target) {
let newItems = [...items];
newItems.splice(index, 0, newItems.splice(draggingIndex, 1)[0]);
setDraggingIndex(index);
setItems(newItems);
}
};
Check out the demo below:
#Making the sortable list more flexible
In the previous section, we created a
`SortableList`
component with a hard-coded list of items. However, to make this component more flexible and reusable, we can modify it to accept a `children`
prop instead. This way, users can have greater control over the content of each list item and customize it to their liking.Below, you'll find an updated version of the
`SortableList`
component with a `children`
prop.tsx
const SortableList = ({ children }) => {
const clonedItems = React.useMemo(() => {
return React.Children.map(children, (child, index) => ({
id: index,
content: child,
}));
}, [children]);
const [items, setItems] = React.useState(clonedItems);
return (
<div>
{items.map((item) => (
<div
key={item.id}
draggable='true'
onDragStart={(e) => handleDragStart(e, item.id)}
onDragOver={(e) => handleDragOver(e, item.id)}
>
{item.content}
</div>
))}
</div>
);
};
In this updated version, we've introduced a new
`SortableListProps`
type that specifies the `children`
prop as a `React.ReactNode`
. Rather than hard-coding the content of each list item, we're now using `React.Children.map()`
to iterate over the children and create an array of items with unique IDs based on their index in the list.This approach enables us to render any type of content inside each list item, including images, links, or custom components. We're also using the
`useMemo`
hook to improve performance by caching the result of a function call and returning that value until one of its dependencies changes.By passing an array containing
`[children]`
as a second argument to `React.useMemo()`
, we're telling React to only recompute the value of `clonedItems`
if the `children`
prop has changed since the last render. This gives users more flexibility to customize the appearance and behavior of our sortable list component.Take a look at the demo below to see these changes in action.
#Conclusion
In conclusion, we've learned how to create a flexible and versatile sortable list component in React by utilizing the HTML 5 drag and drop feature. By using the
`useRef()`
hook and state management, we can easily keep track of the dragged item and update the position of our items accordingly.Furthermore, we've explored how to make our sortable list more reusable by accepting a
`children`
prop, allowing users to customize the content of each list item based on their specific requirements.We hope this post has helped you understand how to implement drag and drop items in a list for your own projects. With these techniques, you can quickly create intuitive interfaces that enable users to sort and organize data with ease.
Questions? 🙋
Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Recent posts ⚡
Newsletter 🔔
If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn