It all started out very simply: I was asked to make it possible for users to trade photos in the gallery. What surprised me when I learned that mobile phone browsers don't have drag & drop (DnD) built in? When I looked at the source, my project was the only one that used DnD. And I decided, for some reason, that my job was similar to the one already done. But it wasn't quite that easy.
The previous implementation was for Y-axis DnD, and it doesn’t work well for the grid.
But at the moment when I realized this, it was already necessary to implement the feature. And I decided to do it simply, following the example of react-beautiful-dnd-grid. In fact, it's just splitting a one-dimensional array into chunks, and then the work goes on as with many one-dimensional arrays.
React's beautiful DND consists of several base components.
Basic usage is with X-axis DnD.
My implementation was just an extension of this behavior, split images into chunks, then every chunk is a row (which is Dropzone), and every image is draggable. When I handle the onDragEnd event in DragContext, I just map the row (chunk) index and the index in a row to a one-dimensional array index.:
const originalIndex = destinationChunkIndex * maxItems + indexInChunk + (sourceChunkIndex < destinationChunkIndex ? -1 : 0);
Everything was implemented, and the feature has already been under the radar for a couple of weeks, but then it was time for a design review, and they tell me that everything is bullshit. Revert it.
In fact, the main problem was that if such a solution looks good with columns, then there are small problems with rows. For example, the fact that photos fly off the screen and, in general, there is no feeling that this is how it should be
If you want, you can play on my playground. It’s possible that such a solution will be suitable for your case.
So I understand that I need a real grid realization that looks like this, so I decided to continue research.
Next, I implemented the feature on the desktop, and everything was very easy there because of the browser support for DnD, so I thought, why not use a polyfill?
But then a surprise was waiting for me with the fact that the ondragover event was not supported in this polyfill, and I could not swap the pictures when the user moved them without additional implementation or working with it by hand. So I decided to skip this variant to save time.
It would seem that my existence is in vain and that I will never succeed. But the moment of truth came, and I saw a notification about a wonderful library on one of the channels in Telegram.
I was happy, looking at the size of the library and how beautifully the examples are implemented in the documentation. I immediately went to look at the examples on the playground. In addition to DnD support for grids, the library also supports accessibility and is very customizable.
I don’t want to observe everything, just the things that are needed for understanding my solution to this problem. I will observe SortableContext, useSortable, DragOverlay, and useSensors, but it is an abstraction over primitives. DndContext, useDroppable, useDraggable (familiar words: it looks the same as react-beautiful-dnd but uses hooks instead of the render children pattern). If you want to know about primitives, look at the docs
SortableContext
provides information via context that is consumed by the useSortable
hook. To be honest, it’s just items and sorting strategy (to understand when it’s needed to reorder items on the screen).useSortable
Hook is an abstraction that composes the useDroppable
and useDraggable
hooks. In my case, I can drop everything everywhere within my image container, and it should be reordered. So here, every rectangle is a place where we can drop an image.DragOverlay
is a great thing for UX and animations. In my case, it shows the place where I will drop an image.useSensors
hook for working with an abstraction to detect different input methods in order to initiate drag operations, respond to movement, and end or cancel the operation. In fact, it’s a helper for event handling (touch, keyboard, or mouse).This example was perfect for me, so I could look at the sources.
However, it was long and difficult to mess with the sources, so I went back to the board.
According to the docs https://docs.dndkit.com/presets/sortable I made a small example with pictures.
Then I realized that the UX without delay is a so-so story and added activation constraints.
However, it just didn't take off, and a context menu popped up in Safari.
So I added user-select: none
Then I needed DragOverlay in order for the user to understand where his picture would be.
Next, I decided that it's worth trying to describe how, in the examples, to make the component sortable so that you can reuse abstraction instead of primitives. It was quite hard, but right now it’s better than in examples.
I am very glad that you have come this far with me. I hope I was able to save you time (it was not an easy but interesting way). I think the future belongs to libraries like dnd-kit, with their small size, good tree-shaking, and customization. I think libraries like react-dnd and react-beautiful-dnd will become a thing of the past. Thank you for your time!