React-dnd: Support dragging multiple elements

Created on 25 Oct 2014  ·  30Comments  ·  Source: react-dnd/react-dnd

There is a common pattern that is hard to implement with native drag and drop: dragging multiple elements. While selection mechanics might vary from app to app (cmd+clicking, checkboxes, predefined groups), but it would be nice to at least make it _possible_ to support this scenario.

screen shot 2014-10-26 at 1 07 07

Because multiple dragged items may not be siblings in DOM and setDragImage(element, x, y) is pretty much insane and hasn't gotten any better, we won't burden ourselves with trying to render several elements in drag preview at once.

How can we help implement this scenario, if we can't show a "multiple" drag preview?

In a way, this scenario is already possible: consumers can manually keep track of selected elements, set dragPreview to some kind of generic placeholder Image and react appropriately to the dropping of (as far as business logic is concerned) several elements.

However there is currently no supported way for one element to know that it's part of a group that's being dragged. From react-dnd's point of view, if we drag something, this component gets getDragState(type).isDragging = true, but other components don't. From your app point of view, if you support multiple selection, you want all logically "selected" items to be aware that they're being dragged, even if only one of them is being actually "DOM-dragged".

What we need is a way for components to tell react-dnd that, “hey, although onDragStart was received by another component, I want to pretend that I'm being dragged too, and have my getDragState(type) mirror dragged component's getDragState(type), and have my endDrag(didDrop) called too so I could do my stuff”.

How would components opt into that?

design decisions

Most helpful comment

If anyone's interested, I implemented a multiple drag / grid view: https://codesandbox.io/s/9j897k0mwy.
You can use cmd/ctrl and shift keys to select multiple items, then drag them around the grid and insert them anywhere.

This is still a demo, but I might abstract the code a little bit more and package it in a component later.

All 30 comments

Not a priority. We can revisit after introducing drag source keys (https://github.com/gaearon/react-dnd/issues/38#issuecomment-73409935).

I'll be interested in this down the road, although I think you might be able to just wrap a section of your component tree with a component that has a dragSource...
Like if you hold shift and click, it expands what sections of the array are copied into the dragItem or something?

Kinda.

First we learn to pass null component (needed for #38) and a different component with same key (needed for #53).

Then we add groupKey which is optional. Like key, it is specified in drag source and is a string. When you begin dragging something with a groupKey, we will call beginDrag not just on the caller, but on every mounted drag source with the same groupKey, and gather their items into an array. We will pass these items instead of item to drop target methods. When dragging is over, we will call acceptDrop on drop target with an item array, and then call each drag source's endDrag with their item.

What's fun is that we can draw DragLayer with a custom “composite” thumbnail component and a counter like I screenshotted above because we know the count from items.length.

Thats a way less ugly idea than what I had in mind

:+1: for this feature :-)

Closing, as I don't currently have a use case for this, and it's a significant implementation effort.

@gaearon

I used Java/Swing DnD back in the day, and they had probably an army of developers who grappled with this problem and came up with a pretty refined, extremely generic and flexible framework. I would recommend researching old desktop frameworks as they probably solved all the difficult challenges with making a good DnD framework like this.

The way to do this in Java/Swing (although it had no built-in support for drag images) was to _make the tree view itself the drag source_, and make it _able to decide whether or not a drag should be initiated based on its current state and the drag start position_. Is monitor.getInitialSourceClientOffset() available when canDrag and beginDrag are called? People will definitely want it to be available. They will probably also want modifier keys (ctrl, alt, shift) to be available and to be able to make drag sources specify whether to move or copy, and drop targets to specify whether they support move and/or copy, based upon any of these factors, because that is possible in Java/Swing and I'm guessing other frameworks. Swing also had the concept of _drag gesture recognizers_ that could be swapped out.

I'd predict that treating all the items in the list as individual draggables will just be difficult compared to having the list component be the single drag source that figures out what kind of drop payload / preview image to construct based upon what's selected in it.

Consider also that a developer might want to _change the order of the items being dragged based upon which item the user clicked and dragged from._ I believe Windows Explorer behaves this way.

Has anyone implemented dragging multiple items with react-dnd in their applications? What's your current solution for this?

did you mean multiple drop target for single drag source?

+1 @danii1 : Has anyone implemented dragging multiple items ?

I implemented it, solution is basically what is described in the first post:

  1. Keep track of your selected items
  2. Implement CustomDragLayer (see https://gaearon.github.io/react-dnd/docs-drag-layer.html) and render your selected items yourself
  3. Handle drop if dropped item is part of the selection

I implemented it by:

  1. Keep track of selected items in a Set
  2. Pass this as a property to the dnd Source
  3. In beginDrag retrieve the Set form the props and return it as the drag item
  4. Create a custom drag preview
  5. On EndDrag move all the items in the Set into the dnd Target

I have a slightly different use case for this I think, I want to draw nodes that are draggable with edges that aren't draggable but do move when nodes they're attached to move. My current implementation is a CustomDragLayer that draws the edges when it detects a DraggableNode is moving (also drawn on the CustomDragLayer but I think it might be better to model it as a multiple drag situation by implementing all edges as DragSources with isDragging() implemented to detect if a node they're attached to is moving so that they can draw themselves instead of having the CustomDragLayer inject the coordinates as props.

@danii1
Can you please share the one you have implemented for a reference

I will write a post and add repo about multiple and nested drag and drop with react-dnd + redux.

@nayemmajhar, it would be great

@nayemmajhar any update on an example with this?

I have developed this application using react DnD with reduxJS long ago https://www.joomshaper.com/page-builder I am writing a tutorial but need time publish

@serle Would you be able to share a code example of how you did this?

@ianmclean2011 check it out here: https://github.com/react-dnd/react-dnd/issues/590

I'm trying to implement multi-drag in an example similar to the sortable example here: https://react-dnd.github.io/react-dnd/examples-sortable-simple.html. I want to be able to select any objects, and then move them as a unit up and down the list. I'm having trouble wrapping my head around how to use the examples/info shared above to do this. Any thoughts/ideas/feedback on how this might work?

If anyone's interested, I implemented a multiple drag / grid view: https://codesandbox.io/s/9j897k0mwy.
You can use cmd/ctrl and shift keys to select multiple items, then drag them around the grid and insert them anywhere.

This is still a demo, but I might abstract the code a little bit more and package it in a component later.

@melvynhills Have you investigated a solution that doesn't require passing all the selected cards to each Card component? I don't think there is a solution considering the Card's beginDrag() needs a reference to all selected Cards.

@jmcrthrs No, it doesn't seem possible to me with react-dnd API. I don't think there's a way to know before dragging which specific item is going to be dragged. That's also the only way to know whether the dragged item is within the multiple selection you made, or if it's outside of that selection that we then need to replace.

@melvynhills @jmcrthrs other desktop dnd frameworks I've used rely on making lists and tables the drag sources/drop targets instead of making each individual row, cell, item etc. its own drag source and/or drop target. The implementation for dragging multiple items, as well as getting the specific drop location within the list or table, comes out a lot cleaner that way. When a list receives a drag start event it can simply look at its current selected items.

@jedwards1211 do you think it's doable with this library? Especially the use-case of starting dragging an item that wasn't selected before the drag start. I'm not sure it's the way react-dnd is supposed to work.

Does anybody solved case in which there are multiple drop targets?
Let's say one is dragging multiple items (using one of the solutions above) and one item ends up on one type of drop target and the other on the different one. drop will be triggered just for drag source which initiated drag, but not for the other one. Any ideas how to trigger drop individually for each item being moved.
Example:
Is development slow, Online Whiteboard for Visual Collaboration 2019-11-27 10-45-33

EDIT: Created separate issue for this one https://github.com/react-dnd/react-dnd/issues/1650

@melvynhills probably if a mousedown handler triggers item selection (which you should be doing, all software with DnD I have used works this way), it will work just fine with React DnD for the container component to be the drag source, because the drag events will come after mousedown.

Have you investigated a solution that doesn't require passing all the selected cards to each Card component?

This is an example of why I would recommend making the container component the drag source instead of the individual items. I haven't needed to implement multiselect with React DnD but maybe sometime I can make a sandbox to convince folks it's the more maintainable approach

Has anyone implemented dragging multiple items with react-beautiful-dnd in their applications? What's your current solution for this?

Was this page helpful?
0 / 5 - 0 ratings