React Image Reordering Grid Drag and Drop (Desktop and Mobile)

Written by antonkrylov322 | Published 2023/04/25
Tech Story Tags: web-development | javascript | typescript | drag-and-drop | tutorial | programming | coding | react

TLDRFull story of my implementation of the DnD feature on mobile devices with examples on codesandbox.via the TL;DR App

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.

React-beautiful-dnd

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.

A short intro to react-beautiful-dnd

React's beautiful DND consists of several base components.

  • Dropzone: this is the place where the element is dropped; the component itself is implemented through the render children pattern.
  • Draggable is a dragged element; the component itself is implemented through the render children pattern.
  • DragContext: a context for handlers (possibly handle several drop zones, etc.)

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. https://codesandbox.io/p/github/pivaszbs/image-reordering-beautiful-dnd/draft/gifted-voice

So I understand that I need a real grid realization that looks like this, so I decided to continue research.

Polyfill realization

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?

https://www.npmjs.com/package/mobile-drag-drop?embedable=true

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.

DnD kit

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.

https://dndkit.com/?embedable=true

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.

A short intro to DnD-kit

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

  • SortableContextprovides 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).
  • useSortableHook 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.
  • DragOverlayis a great thing for UX and animations. In my case, it shows the place where I will drop an image.
  • useSensorshook 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).

Real-grid DnD implementation

This example was perfect for me, so I could look at the sources.

https://codesandbox.io/p/github/pivaszbs/image-reordering-beautiful-dnd/draft/gifted-voice

However, it was long and difficult to mess with the sources, so I went back to the board.

Step 1

According to the docs https://docs.dndkit.com/presets/sortable I made a small example with pictures.

https://codesandbox.io/p/sandbox/react-image-reordering-grid-mobile-forked-w0wlnh?file=%2Fsrc%2FApp.tsx

Step 2

Then I realized that the UX without delay is a so-so story and added activation constraints.

https://codesandbox.io/p/sandbox/react-image-reordering-grid-mobile-forked-w0wlnh?file=%2Fsrc%2FApp.tsx

Step 3

However, it just didn't take off, and a context menu popped up in Safari.

So I added user-select: none

https://codesandbox.io/p/sandbox/react-image-reordering-grid-mobile-forked-dwi8cb?file=%2Fsrc%2FImage.tsx

Step 4

Then I needed DragOverlay in order for the user to understand where his picture would be.

https://codesandbox.io/p/sandbox/react-image-reordering-grid-mobile-forked-revjso?file=%2Fsrc%2FApp.tsx

Step 5

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.

https://codesandbox.io/p/sandbox/react-image-reordering-grid-mobile-ssnzd2?file=%2Fsrc%2FApp.tsx

Conclusion

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!

Useful links

  1. https://dndkit.com/
  2. https://docs.dndkit.com/
  3. https://master--5fc05e08a4a65d0021ae0bf2.chromatic.com/?path=/story/presets-sortable-grid--basic-setup
  4. https://github.com/react-dnd/react-dnd
  5. https://github.com/atlassian/react-beautiful-dnd
  6. https://www.npmjs.com/package/mobile-drag-drop

All sandboxes, step by step

  1. https://codesandbox.io/p/github/pivaszbs/image-reordering-beautiful-dnd/draft/gifted-voice
  2. https://codesandbox.io/p/sandbox/react-image-reordering-grid-mobile-forked-w0wlnh?file=%2Fsrc%2FApp.tsx
  3. https://codesandbox.io/p/sandbox/react-image-reordering-grid-mobile-forked-1thxdp?file=%2Fsrc%2FApp.tsx
  4. https://codesandbox.io/p/sandbox/react-image-reordering-grid-mobile-forked-dwi8cb?file=%2Fsrc%2FImage.tsx
  5. https://codesandbox.io/p/sandbox/react-image-reordering-grid-mobile-forked-revjso?file=%2Fsrc%2FApp.tsx
  6. https://codesandbox.io/p/sandbox/react-image-reordering-grid-mobile-ssnzd2?file=%2Fsrc%2FApp.tsx


Written by antonkrylov322 | Frontend developer with taste of beer
Published by HackerNoon on 2023/04/25