2021-10-19
A quick tutorial on how to implement reordering (in a grid or otherwise) using drag and drop in modern React functional components with hooks.
With more and more web applications supporting touch intuitive UI, a drag-drop functionality is quite common to have. There's a lot of libraries available for this, built on top of the HTML drag-drop API, using DOM event model, triggered from a mouse event.
On a higher level, the operation begins with the user selecting a draggable element, dragging it over to a drop target, and dropping the element by releasing the mouse or touch. We could achieve the desired functionality by implementing event handlers for dragEnter
, dragOver
, dragLeave
and drop
event types. This is covered partly in the file upload front-end tutorial. Here we're going to focus on React-DND, a light-weight library adding drag-drop functionality to functional components.
Reordering Items with Drag-Drop in React
This is what we'll be implementing in this tutorial.
For sake of visibility and demonstration, we'll use an ID
and a position
field in the Item
model.
We're keeping a position key to hold the position of the item in the array (card in the list). We are going to re-assign the position of the item on a successful drop, so the order is maintained. This will also help in demonstrating which items are being swapped.
Assuming you already have a list of items setup in a grid in appropriate components (e.g. a <CardContainer {...}>
and a <CardItem item={item} {...}>
), let's get started. This shouldn't take long.
Start by installing react-dnd, with HTML5 backend to your project.
Now our cards themselves will serve as the drag source and drop targets. Note that we're planning to drag the card and drop it where another card exists.
The card container is driving the state for the cards — the order in which the items will be rendered. We will need to implement two methods to handle hovering and dropping.
In card container:
We're essentially swapping the card being dragged (draggedItem
) with the card it was hovering over. You could use immutability-helper to help out with the splicy bit.
The reason we're performing the swap on hover, rather than on the actual drop is we want to give a visual feedback of the action — by either utilizing opacity or move animations that the card you are dragging is bring put in it's new location.
Once dropped, we'll modify the position of the items.
If you have some action dependent on the order of the items, it'll be a good practice to have the container component maintain it's copy of
items
in state, and only modify the copy ofitems
from it's parent on dropping.
Now for the container, we just need to wrap it around a DndProvider, so it enables dragging/dropping within itself.
Inside CardItem
we need to enable support for dragging and dropping. Start by getting a ref
to the card element.
Now we'll implement dragging support on the card using react-dnd's useDrag. This hook emits out a dragging state, which we can use to modify the styles to display.
And we can use the isDragging
prop in our render function and animate dragging state like:
Next we'll implement dropping with useDrop:
Now we just need to connect drag
and drop
with the ref
.
And add the handlerId to the element.
It should be working as expected now.
...and that's all for this short drag-drop tutorial. Make sure to check out the examples at react-dnd. You can add the Touch Backend supporting touch events along with HTML mouse events for example.