react-sortable-tree icon indicating copy to clipboard operation
react-sortable-tree copied to clipboard

You may not call monitor.canDrop() inside your canDrop() implementation

Open zourbuth opened this issue 6 years ago • 15 comments

In canDrop, before returning false, we want to update state to display notification about why it can't be dropped, but got this error:

Uncaught Error: You may not call monitor.canDrop() inside your canDrop() implementation. Read more: http://react-dnd.github.io/react-dnd/docs-drop-target-monitor.html

Is there a way to deal with it or may be a callback function?

zourbuth avatar Mar 06 '18 16:03 zourbuth

Hi @zourbuth Do you mind posting a repro? So I can better understand your issue

wuweiweiwu avatar Mar 12 '18 05:03 wuweiweiwu

I've tested using Storybook example Drag from external source to prevent drop Baby Rabbit into Papa Rabbit, then shows message why it is prevented.


class UnwrappedApp extends Component {
  constructor(props) {
    super(props);
    this.dropMessage = this.dropMessage.bind(this);  
    this.state = {
      treeData: [{ title: 'Mama Rabbit' }, { title: 'Papa Rabbit' }],
      message: 'Sample message',
    };
  }
  
  dropMessage(object) {
    if ( object.nextParent && object.nextParent.title === 'Papa Rabbit' ) {
      this.setState({ message: 'Papa Rabbit is sleeping, play outside!' }); // --> ERROR HERE
      return false;    
    }
	
    return true;
  }
  
  render() {
    return (
      <div>
        <div className="message">{this.state.message}</div>
        <div style={{ height: 300 }}>
          <SortableTree
            treeData={this.state.treeData}
            onChange={treeData => this.setState({ treeData })}
            canDrop={object => this.dropMessage( object )}
            dndType={externalNodeType}
          />
        </div>
        <YourExternalNodeComponent node={{ title: 'Baby Rabbit' }} />
        ← drag this
      </div>
    );
  }
}

zourbuth avatar Mar 12 '18 14:03 zourbuth

@zourbuth this issue is confirmed and I think its due to how we bind this when we create DropTarget. I will have to investigate more later

Seems related to https://github.com/react-dnd/react-dnd/issues/265

I will consider possibly writing so canDrop to allow a callback. Or possibly passing the same object to onDragStateChanged. Seems to be a tricky issue

wuweiweiwu avatar Mar 12 '18 20:03 wuweiweiwu

+1 for being able to specify the reason why something can't be dropped - sometimes it can be the result of some complex logic rules and users could be frustrated not knowing why.

I'm not too convinced however about altering the state from inside a function named canDrop which is supposed to be pure and just return information.

What about something like allowing the canDrop callback to return not just a boolean but also an object like { canDrop: false, tip: "xxx" }, which could be rendered inside the red box?

  dropMessage(object) {
    if ( object.nextParent && object.nextParent.title === 'Papa Rabbit' ) {
      return { canDrop: false, tip: 'Papa Rabbit is sleeping, play outside!' };
    }
    else if ( /* some other condition */ ) {
      return { canDrop: true, tip: 'Oh yes drop me here please' };
    }
    return true;
  }

spcfran avatar Apr 25 '18 09:04 spcfran

I was thinking allowing the canDrop callback to have access to the node and/or location it is going to be dropped as parameters, thus the user can use that information to render a message or whatever they want to do

wuweiweiwu avatar Apr 26 '18 08:04 wuweiweiwu

#261 appears to cover a similar issue that could be addressed by these solutions.

buzztone avatar May 03 '18 01:05 buzztone

@wuweiweiwu we would like to help, but don't know where to start, what piece of code need to modify. Perhaps you can point it to us.

zourbuth avatar May 25 '18 02:05 zourbuth

hi @zourbuth that would be much appreciated! https://github.com/frontend-collective/react-sortable-tree/blob/master/src/utils/dnd-manager.js#L146 is where the this.props.canDrop is called.

This would requiring allowing the user specifying a callback parameter in canDrop can calling it with the required parameters (ex: node, location)

wuweiweiwu avatar May 29 '18 06:05 wuweiweiwu

Hi @wuweiweiwu, in this case, I didn't see the line as the problem. I found the line https://github.com/frontend-collective/react-sortable-tree/blob/master/src/utils/dnd-manager.js#L252 which calls canDrop inside canDrop prop.

function nodeDropTargetPropInjection(connect, monitor) {
  const dragged = monitor.getItem();
  return {
    connectDropTarget: connect.dropTarget(),
    isOver: monitor.isOver(),
    canDrop: monitor.canDrop(), // <--- calls canDrop inside canDrop
    draggedNode: dragged ? dragged.node : null,
  };
}

I understand the DnD collect parameter above that passed canDrop is used for cancel pad class in placeholder-renderer-default.js and node-renderer-default.js.

If we remove canDrop from the collect parameter, it works, the message is updated, but always shows cancel pad instead of landing pad.

Please suggest.

zourbuth avatar Jun 02 '18 14:06 zourbuth

@zourbuth I was thinking of adding a callback, however, in hindsight, it probably is not a good long term fix. I will be updating our react-dnd dependency soon #87 (it is a major change) So I will revisit this issue once that is done.

Thanks!

wuweiweiwu avatar Jun 11 '18 17:06 wuweiweiwu

Hello @wuweiweiwu.

Any update with this issue?

Thanks.

zourbuth avatar Mar 11 '19 16:03 zourbuth

I did a hacky workaround for this issue, since showing a rejection notification in canDrop() was triggered multiple times, so i got dozens of rejection notifications when only one was neccessary.

so i have a variable in state which just records the last success or failure of canDrop.

then in onDragStateChanged if its not isDragging i check the variable from state, and display the error notif, if it was a failure. For a complete drag action, onDragStateChanged is triggered once so it works.

kohsah avatar Nov 19 '19 11:11 kohsah

have some update ?

wsz7777 avatar Dec 13 '21 06:12 wsz7777

I m also facing the same issue. CanDrop() called multiple times while we drop the external node into tree. Please suggest some workaround.

mak009 avatar Mar 05 '22 18:03 mak009

generateNodeProps={() => ({
  onDragEnd: () => {
    // Works if canDrop returns false
    // Add code here
  },
})};

I think until this issue is resolved this workaround might be a good solution.

eugenrecaj avatar Mar 07 '22 15:03 eugenrecaj