Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot drag element twice. Uncaught Invariant Violation: Cannot call beginDrag while dragging. #455

Closed
gabrielbull opened this issue May 5, 2016 · 23 comments

Comments

@gabrielbull
Copy link

gabrielbull commented May 5, 2016

os OS X El Capitan Version 10.11.4 (15E65)
browser Google Chrome Version 51.0.2704.36 beta (64-bit)
react-dnd 2.1.4
react-dnd-html5-backend 2.1.2

Error happens after a drag and drop where the source element has been removed after the drop when trying to drag the element again.

capture d écran 2016-05-05 a 14 18 14

@gabrielbull gabrielbull changed the title Uncaught Invariant Violation: Cannot call beginDrag while dragging. Cannot drag element twice. Uncaught Invariant Violation: Cannot call beginDrag while dragging. May 5, 2016
@MatthewDavidCampbell
Copy link

@gabrielbull Assuming you might be doing the same thing I was. Namely, you have some list and the items are wrapped as both DropTarget and DragSource. I was getting the same error. From the target specification, the drop function called a swap action but then it was if the element was from then on always monitored as dragable.

So I added an endDrag function to the source specification and consoled out when it got called. Never was the result. So the monitor never is made aware that dragging has finished. Likely, a tick cycle problem since the issue disappears then debugging with break points.

Rather than trying to understand the logic in the HTML5 Backend code, I changed the drop (or hover if that is where you make swaps) to return an identifier of the item being dropped on to. The return value can be captured by the endDrag (monitor.getDropResult()) and the swap executed from endDrag.

This solved my problem but I would love for somebody else to weight in on the original problem.

@twobit
Copy link

twobit commented Jun 5, 2016

Looks like the same issue as #442, components which are both DropTarget and DragSource.

@vegetableman
Copy link

vegetableman commented Jun 20, 2016

So I added an endDrag function to the source specification and consoled out when it got called. Never was the result.

@MatthewDavidCampbell Have been trying to reproduce this issue. Am guessing you mean the endDrag function is not called. That is, dragend event on the backend is never fired.

But then you have mentioned, you moved the code from the drop event to endDrag event in order to execute the swap. Could you please elaborate more on this?

@ndao
Copy link

ndao commented Jul 6, 2016

@vegetableman, i had the same issue and fixed it with @MatthewDavidCampbell solution
here's the old dropTarget

const dropTarget = { 
  canDrop (props) {
    return true
  },

  drop (target, monitor) {
    someFluxAction(target)
  }
}

to fix it, i'm returning an identifier to be read by endDrag and moved someFluxAction(target) to inside endDrag

const dropTarget = { 
  canDrop (props) {
    return true
  },

  drop (target, monitor) {
    return target.id
  }
}

const dragSource = {
  beginDrag(props) {
    return props
  },

  endDrag: function (props, monitor, component) {
    if (!monitor.didDrop()) {
      return;
    }
    var dropResult = monitor.getDropResult();
    someFluxAction(dropResult)
  }
}

hope that helps.
-n

@vegetableman
Copy link

Thanks @ndao for chipping in.

The issue we were facing was due to this rogue class NonNativeHTML5Backend:- https://gist.github.com/nickpresta/eb5cce69d650db4c2795.

If a native item was dragged in, the above class used to return, leaving the state of dnd incomplete and the END_DRAG action as a result was never dispatched, leading to the beginDrag error mentioned above.

Solution:- We removed the above class and the code was using react-dropzone for native directories. That's no longer the case. I modified the html5 backend to send callback with directory items as well. So, we are using a single lib and backend now for drag and drop needs.

@vegetableman
Copy link

To anyone facing this issue, add console logs in dnd-core actions. That way, it's easily debuggable as to when the state breaks.

@Aklesk
Copy link

Aklesk commented Aug 10, 2016

On the off chance it's helpful to anyone with this issue, I ran into this error message recently and it turned out to be because I was generating my components from a map command, and I was assigning them randomly-generated keys. When the list re-ordered, the keys would be re-generated randomly, so React DnD was losing connection to the components.

I changed my map commands to use deterministic keys that would stick with the component even after re-ordering, and the issue went away instantly. Obvious in hindsight, but frustrating until then!

@deepsweet
Copy link

same problem here, but the solution above doesn't work with hover method – there is no way to return something from it and then get the result with monitor.getDropResult() in endDrag.

@deepsweet
Copy link

I have figured it out (at least for my specific case): I just improperly created completely new DropTarget(...)(DragSource(...)(MySortableItem)) instances every time inside a map() while rendering MySortableList. So after every order state change MySortableItems were unmounted and that's why react-dnd was unable to fire END_DRAG event – original DOM nodes were detached and replaced.

Just check your items with componentWillUnmount.

@kesne kesne added the triage label Aug 20, 2016
@jiajiawang
Copy link

Had the same problem here.
Having a list of draggable and droppable items, and user can drag items to reorder.

Thanks @deepsweet for pointing out the issue.
I used index as the key of the list items which would cause the re-mount of list items when order changes.
Using unique identities as the key fixed the problem.

@siemiatj
Copy link

siemiatj commented Feb 6, 2017

I was using v4 for generating keys but that didn't help.

@mweibel
Copy link

mweibel commented Feb 28, 2017

Had exactly the same problem, thanks for pointing out an easy fix. I used the same approach as @jiajiawang and generate a random id for each item in the array I have sortable.

I wonder if the examples for sortable should mention that (or somewhere else). I spent now ±2 hours finding that by debugging with console.log. I think that could be avoided with some docs. I'd be willing to contribute that if somebody points me to where the best place for that would be :)

@nottoseethesun
Copy link

nottoseethesun commented Mar 8, 2017

@ndao 's solution worked for me, but does not provide the during-hover action that I am hoping for.

When a user would like to re-arrange the items in a list, it is a good user experience to swap the items as they hover, without requiring the user to drop (endDrag, which I was hoping was actuated after a stopping of dragging rather than simply a drop after dragging) to get the desired effect.

But whenever I try to use hover or isDragging, I get the dreaded error that is the subject of this thread. That error kills any subsequent drag-and-drop in my list. And I hope to not have to implement an entirely separate representation of the items (outside of the drag container, to just show the swapping before actually swapping the items in the actual draggable list) to get the desired effect.

Interestingly, when I debounce hover to about 20ms, sometimes the error is not seen until a few iterations of the action sequence. Additionally, at larger debounce intervals, getItem returns undefined. So those results indicate that the underlying issue may be a race condition.

I seek suggestions as to how to do error-free draggable item list mutation in isDraggable or similar.

@theTechie
Copy link
Member

@nottoseethesun - i believe you want to do this

  endDrag: function (props, monitor, component) {
    const {id} = monitor.getItem() 
    if (!monitor.didDrop()) {
      props.moveItem() // you need to keep re-arranging your items
    }
    var dropResult = monitor.getDropResult();
    someFluxAction(dropResult)
  }

Reference: Sortable

@nottoseethesun
Copy link

nottoseethesun commented Mar 9, 2017

@theTechie - Apparently in endDrag, monitor.didDrop() is always true, so the item never gets moved. Still seeking a solution.

@szszoke
Copy link

szszoke commented Mar 17, 2017

@deepsweet

I have figured it out (at least for my specific case): I just improperly created completely new DropTarget(...)(DragSource(...)(MySortableItem)) instances every time inside a map() while rendering MySortableList. So after every order state change MySortableItems were unmounted and that's why react-dnd was unable to fire END_DRAG event – original DOM nodes were detached and replaced.

Just check your items with componentWillUnmount.

Could you post a code snippet or is there working example?

@theTechie
Copy link
Member

theTechie commented Mar 17, 2017

@nottoseethesun - You are right. monitor.didDrop() is always true in endDrag.
i think i was not very clear in my explanation. I missed the DropTarget part.
I am suggesting a solution like below.

const dropTarget = { 
  canDrop (props) {
    return true
  },

  hover(props, monitor) {
    const { draggedId, item } = monitor.getItem()
    const { overId } = props

    if (draggedId !== overId) {
        const overIndex = props.findItemIndex(overId)
        props.moveItem()  // you need to keep re-arranging your items
    }
  }
}

const dragSource = {
  beginDrag(props) {
    return props
  },

  endDrag: function (props, monitor, component) {
    if (!monitor.didDrop()) {
      props.moveItem()
    }
    var dropResult = monitor.getDropResult();
    someFluxAction(dropResult)
  }
}

@nottoseethesun
Copy link

nottoseethesun commented Mar 21, 2017

Thanks @theTechie - but monitor.getItem() is always null. Also I have no overId property. Also, in that example, !monitor.didDrop() is used as a condition, but you've verified that it's always true there.

@deepsweet , unfortunately at this time I am not likely able to create a small test reproduction of the issue. I am definitely not re-creating the instances.

@szpytfire
Copy link

For any future readers; I also sat staring blankly at my code wondering what was going wrong until I realised that even though my Sortable (i.e. Card) components were configured correctly, the problem lay with the fact that the components were being instantiated in the parent's render method. This meant that whenever the parent component re-renderered, react-dnd would lose the connection to the child Sortable components.

@optimatex
Copy link

hi, @nottoseethesun How exactly did you debounce hover to about 20ms?

@stale
Copy link

stale bot commented Jul 6, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Jul 6, 2019
@stale stale bot closed this as completed Jul 13, 2019
@abrenoch
Copy link

Just wanted to add my experience for any future travelers.

I had a component that contained a <Droppable /> that would be conditionally rendered (separate from the rest of the droppables) once dragging started. I had to change this behavior to hide the component rather than unmounting it, otherwise when dragging stopped the error OP mentioned would show up.

Hope that helps someone!

@lichaar
Copy link

lichaar commented Aug 19, 2020

Fixed it. Just make sure that if you're iterating through an array to display your components and your components have dnd in them (either direct or their children) to add a key to your components, and don't use iterator index as key, use proper unique key. Hope this helps out some one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests