How to make and test your own React drag and drop list with 0 dependencies

--

Photo by Kaitlyn Baker on Unsplash

This article is originally published on Tinloof.

I know, drag and drop is a solved problem. There are several great utilities you can use to easily have a drag and drop list in your application (dragula, react-beautiful-dnd, and react-dnd). These libraries offer APIs that make it easy to meet your needs without worrying about what is happening under the hood. Most of them hide a complex logic to make the drag and drop work properly on several devices, whether touch or not.

However, using external dependencies comes at a cost. In fact, it increases the bundle size of your application, which can affect its overall performance. You also become dependent on the maintenance of the library.

Therefore, before rushing to use a library, you should take a look at your problem and ask yourself if there is a simple way to solve it without relying on a library.

For instance, if your application does not target touch devices and you want to keep your bundle size as lean as possible for an optimal performance, you should consider implementing your own drag and drop component.

This is exactly what this article will guide you through. You will be using the native HTML5 Drag & Drop API to create and test your own drag and drop sortable list:

Final result

The final source code & tests can be found here.

1. Creating the view

In this first iteration, we will just create the view without any drag & drop behavior.

The state is composed of a list of food items. The food items are unique, so we can use them as keys when rendering the list items. The uniqueness of the food items will also be used later in the sorting logic.

The styles are basically just to color the container and the list items( <li> ), set the padding in them, and set a move cursor in the drag div (containing the hamburger icon) to make it clear to the user that it’s draggable.

Creating the view: App.js
Creating the View: styles.css
Creating the View: Result

2. Making the items draggable

To make the items draggable, we add the draggable (shorthand for draggable={true}) prop to the drag div.

Adding the draggable attribute: App.js
Adding the draggable attribute: Result

Although the icon is draggable, it’s not quite yet what we want to achieve. We want the whole item row to be draggable through the icon.

To do so, we need to specify what we want to be draggable in the onDragStart event handler:

Making the items draggable: App.js

We set the drag effect to be ‘move’. This indicates that the visual effect will be moving the item.

e.dataTransfer.setData("text/html", e.parentNode) sets the dragged item to be the parent node of the drag div, being the list item. This is necessary for browsers like Firefox to achieve our desired effect.

e.dataTransfer.setDragImage(e.parentNode, 20, 20) does the same thing as the previous method. This is necessary for Chrome to achieve our desired effect.

Making the items draggable: Result

Our items are draggable through the drag icon! We want now to make dragging items affect their sorting.

3. Changing the sorting of the items

When an item has another item dragged over it, we need to react (no pun intended 😅) to that by changing the sorting of the list. If item A is dragged over item B, then item A gets placed after item B.

First, we should store the currently dragged item when the dragging starts (i.e. in the onDragStart event handler).

Then we should set the currently dragged item to null once the dragging is done. This can be done in the onDragEnd event handler, which is called once the user stops dragging.

We should also implement the sorting logic in the onDragOver event handler for each list item, which is called whenever an item has an element dragged over it.

Changing the sorting of the items + setting & resetting the dragged item: App.js

If the dragged-over item is the same as the currently dragged item, nothing changes. Otherwise, we move the currently dragged item from its initial position in the array of items to be right after the dragged-over item.

Here you go, our list items are now draggable and sortable:

Final result

4. Testing

Drag and drop is a complex interaction and is hard to evaluate with a simple unit test.

We might think of using mock function calls in a unit test and check that the state of our component changed properly. While this would work, it remains a non-reliable test because it does not assure that the behavior is working as it’s supposed to be.

Our test should be as close as possible to how a user interacts with the drag and drop. Thus, we will write an end-to-end test that simply reproduces the dragging behavior and checks whether the sorting of the list changed properly. One library we can use is cypress.js.

After installing Cypress through npm or yarn, we can simply add a test file dragAndDrop.js to the cypress/integration directory:

The test drags the first item to the third position using the dragItem helper function. It then checks whether the list sorting has changed properly by checking the values of the affected list items.

The dragItem helper function drags the item at index indexToMove to the position at targetIndex . To do so, it triggers a dragstart event on the drag div at indexToMove(with a mock dataTransfer object so that our dragstart event handler doesn’t fail) and a dragover event on the list item as targetIndex .

The getListItemValue helper function gets the content DOM element inside the list item at the given index .

We can then run the test through:

./node_modules/.bin/cypress run

A nice benefit of using Cypress is that it quickly provides a video of the integration test once you run it:

Test video using Cypress

You can find more articles on Tinloof.

--

--