← Back toDrag and drop in React

Display a placeholder in a sortable list

Written byPhuoc Nguyen
Created
03 Dec, 2023
Tags
HTML 5 drag and drop, placeholder, sortable list
In our previous post, we learned how to create a sortable list that allows users to arrange items by dragging and dropping them into the desired position. However, it can be frustrating and confusing for users to know exactly where an item will go when they start dragging it. This is especially true when there are many items on the list or if they all look similar.
To make things easier for users, we can add a placeholder. A placeholder is a visual cue that shows users where the dragged item will end up when they drop it. It provides feedback and helps users anticipate the final result. By using a placeholder, designers can help users feel more confident and in control when working with sortable lists.
In this post, we'll show you how to add a placeholder to a sortable list. This simple addition will make a big difference in the user experience and help ensure that your sortable list is easy to use and understand.

Detecting when users drag an item over another

When a user drags an item over another one, we want to display a placeholder. To determine when this happens, we can create a new state variable called `dragOverIndex` and initialize it to `-1`. This value indicates that no item is currently being dragged over.
ts
const [dragOverIndex, setDragOverIndex] = React.useState(-1);
Let's take a look at how we manage this state during drag and drop operations.
When the user drags an item over another one, the `handleDragOver()` function is called. This function takes two arguments: `e`, which represents the event, and `index`, which is the index of the current item in the list.
First, we check if the target node is not the same as the drag node. If they are different, it means that we are dragging an item over another one. Next, we update the `dragOverIndex` state with the index of the current item.
Then, we create a copy of our `items` array using spread syntax and remove the dragged item from its original position using `splice()`. We then insert it into its new position using another call to `splice()`. The new position is determined by checking whether any other items are currently being dragged over (`dragOverIndex !== -1`). If no items are being dragged over, we place it at the end of the list (`items.length`). Otherwise, we place it at the index specified by `dragOverIndex`.
Finally, we update both our `draggingIndex` and `items` states with their new values to reflect this change in position. This way, we can ensure that our app is always up-to-date with the latest changes made by the user during drag and drop operations.
ts
const handleDragOver = (e, index) => {
if (dragNode.current !== e.target) {
setDragOverIndex(index);
let newItems = [...items];
newItems.splice(
dragOverIndex === -1 ? items.length : dragOverIndex,
0,
newItems.splice(draggingIndex, 1)[0]
);
setDraggingIndex(dragOverIndex === -1 ? items.length - 1 : dragOverIndex);
setItems(newItems);
}
};
Once the user stops dragging the item, it's important to reset both the `draggingIndex` and `dragOverIndex` states. This ensures that we don't show any unnecessary placeholders, and that future drag and drop actions aren't affected by leftover state.
To achieve this, we can use a function called `handleDragEnd()` to handle the `dragend` event. Inside this function, we simply set both states back to their initial values. This way, we're always starting fresh and ready for the next drag and drop action.
ts
const handleDragEnd = () => {
setDraggingIndex(-1);
setDragOverIndex(-1);
};
We can use this new state variable to show a placeholder element instead of the item being dragged. In this updated version of our component, we only render a placeholder `div` when the current index matches the `dragOverIndex`. This tells us that the user is dragging an item over the placeholder.
tsx
<div>
{
items.map((item, index) => (
<div
onDragStart={(e) => handleDragStart(e, index)}
onDragOver={(e) => handleDragOver(e, index)}
onDragEnd={handleDragEnd}
>
{
index === dragOverIndex
? <div className="placeholder" />
: item.content
}
</div>
))
}
</div>
To make the placeholder more visible, let's add a dashed border to it. To do this, we can set the `border` style property to `dashed` in our CSS file. This will help the placeholder stand out from the rest of the list items and make it clear where the dragged item will be dropped.
You have the freedom to customize the appearance of the placeholder to match your design. So go ahead and tweak it until it looks just right!
css
.placeholder {
border: 2px dashed rgb(203 213 225);
}
Take a look at the demo below. Give it a try by dragging an item within the list and you'll notice a placeholder appear.

Changing the size of the placeholder

Currently, the size of the placeholder element is hard-coded, as you can see in the CSS class.
css
.placeholder {
height: 8rem;
width: 16rem;
}
In reality, the size of the placeholder should match the size of the dragging item. To achieve this dynamically, we can use an internal state with two properties, `w` and `h`, which are both set to 0 by default.
ts
const [placeholderDimensions, setPlaceholderDimensions] = React.useState({
h: 0,
w: 0,
});
To ensure that our placeholder element has the same dimensions as the item being dragged, we can pass the `clientWidth` and `clientHeight` of the dragging item to our `handleDragStart()` function. By doing so, we can set the width and height of our placeholder element accordingly.
Here's how we can modify the `dragstart` event handler code to achieve this:
ts
const handleDragStart = (e, index) => {
const { target } = e;
const { clientWidth, clientHeight } = target;
setPlaceholderDimensions({ w: clientWidth, h: clientHeight });
};
In the updated version of our `handleDragStart()` function, we're using destructuring to extract the `clientWidth` and `clientHeight` properties from the dragging item. We'll then set the `placeholderDimensions` state with an object that contains these values.
Now, we'll update our JSX to use these dimensions when rendering our placeholder element.
tsx
index === dragOverIndex
? <div
className="placeholder"
style={{
width: `${placeholderDimensions.w}px`,
height: `${placeholderDimensions.h}px`,
}}
/>
: item.content
We're making some changes to the way our placeholder element is rendered. Now, the placeholder will only appear when the current index matches the `dragOverIndex`. We're also setting the width and height of the placeholder element based on the dimensions stored in our state variable. These changes will make the placeholder the same size as the dragging item, giving users a clearer visual cue for where their item will be placed.
Take a look at the demo below to see these changes in action.

Conclusion

In this post, we've showed you how to make your app's sortable lists more user-friendly with a placeholder. With a placeholder, users can easily drag and drop items without losing track of where they're going.
First, we detect when a user drags an item over another one and create a new state variable called `dragOverIndex` to keep track of the item's index. We then use this variable to display a placeholder element in the correct position.
To make the placeholder more visible, we add a dashed border using CSS and dynamically adjust its size to match the dragging item.
By following these simple steps, you can create a more intuitive and user-friendly interface for sorting lists in your app. With just a few lines of code, you can significantly improve your app's usability and enhance the overall user experience.
If you found this post helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

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 🥷.
Ask me questions

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