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

react-dnd can call into an unmounted component causing Uncaught Error : Could not find a valid targetId #1368

Closed
mx2323 opened this issue Jun 2, 2019 · 4 comments
Labels

Comments

@mx2323
Copy link

mx2323 commented Jun 2, 2019

Describe the bug
it is possible for react-dnd to call a collect function that causes a component to unmount, then make another collect() call on the unmounted component, causing an Uncaught Error "Could not find a valid targetId'.

Redux on it's dispatch() of backend Drag And Drop events makes a copy of all listeners to subscribed events at the beginning and always calls them regardless of what redux subscriptions have been unsubscribed while notifying subscribers (see https://stackoverflow.com/questions/43356080/redux-unsubscribe-within-componentwillunmount-still-calls-subscribe-callback). This means that if something like an 'EndDrag' html5 drag event triggers a notification in handleChange() in react-dnd/src/decorateHandler.tsx:handleChange(), and the first collect() call causes an unmount of a component, the subsequent unmounted component may have its collect() call triggered in a subsequent notification of the same event.

Thus, the invariant 'Expected to find a valid target' is triggered because the component validly has been removed from the registry of valid target Id's, but the notification is calling into an unmounted component.

I discovered this issue while attempting to integrate react-sortable-tree, which uses react-dnd into my app. You can see an example of the issue and a recreatable test case here : frontend-collective/react-sortable-tree#490. A drag and drop that gets cancelled by releasing a dragSource outside of the dragTarget reproduces this issue everytime.

The uncaught error and stack trace is pasted below, The stack trace shows that the connect function is calling into monitor.canDrop() which fails on unmounted components.
browser.js:38 Uncaught Invariant Violation: Expected to find a valid target.
at invariant (https://xzoq6xprlz.codesandbox.io/node_modules/invariant/browser.js:38:15)
at DragDropMonitorImpl.canDropOnTarget (https://xzoq6xprlz.codesandbox.io/node_modules/dnd-core/lib/cjs/DragDropMonitorImpl.js:67:9)

at DropTargetMonitorImpl.canDrop (https://xzoq6xprlz.codesandbox.io/node_modules/react-dnd/lib/cjs/DropTargetMonitorImpl.js:24:41)
at nodeDropTargetPropInjection (https://xzoq6xprlz.codesandbox.io/node_modules/react-sortable-tree/dist/index.cjs.js:2367:28)
at DragDropContainer.getCurrentState (https://xzoq6xprlz.codesandbox.io/node_modules/react-dnd/lib/cjs/decorateHandler.js:116:29)
at DragDropContainer._this.handleChange (https://xzoq6xprlz.codesandbox.io/node_modules/react-dnd/lib/cjs/decorateHandler.js:45:39)
at handleChange (https://xzoq6xprlz.codesandbox.io/node_modules/dnd-core/lib/cjs/DragDropMonitorImpl.js:27:21)
at dispatch (https://xzoq6xprlz.codesandbox.io/node_modules/redux/lib/redux.js:220:7)
at Object.eval [as endDrag] (https://xzoq6xprlz.codesandbox.io/node_modules/dnd-core/lib/cjs/DragDropManagerImpl.js:67:21)
at HTML5Backend.handleTopDragEndCapture (https://xzoq6xprlz.codesandbox.io/node_modules/react-dnd-html5-backend/lib/cjs/HTML5Backend.js:145:31)
``

Workaround

Calling into unmounted components is probably not the right behavior, so I added an if statement in react-dnd/src/decorateHandler.tsx:handleChange that drops the notification if the decorateHandler has already been unsubscribed. diff attached.

diff --git a/packages/react-dnd/src/decorateHandler.tsx b/packages/react-dnd/src/decorateHandler.tsx
index 85385ec..bcd149e 100644
--- a/packages/react-dnd/src/decorateHandler.tsx
+++ b/packages/react-dnd/src/decorateHandler.tsx
@@ -159,6 +159,15 @@ export default function decorateHandler<Props, CollectedProps, ItemIdType>({
                }
 
                public handleChange = () => {
+                       if (this.disposable.isDisposed) {
+                               console.log("in handleChange")
+                               //because redux takes a snapshot of all subscribers to 
+                               //events when it starts dispatch, it is still possible to call into this even after 
+                               //the subscription has been unsubscribed
+                               //to prevent against calling into unmounted objects, return immediately
+
+                               return
+                       }
                        const nextState = this.getCurrentState()
                        if (!shallowEqual(nextState, this.state)) {
                                this.setState(nextState)
@stale
Copy link

stale bot commented Aug 2, 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 Aug 2, 2019
@stale stale bot closed this as completed Aug 9, 2019
@hellofantastic
Copy link

hellofantastic commented Aug 13, 2019

@mx2323 was this pushed in? Even if it was the react-sortable-tree still needs latest react-dnd updates, I think its in waiting

@mx2323
Copy link
Author

mx2323 commented Dec 28, 2019

i didnt end up pushing it.

@paillave
Copy link

Is there any way to have it working? What is the workaround?

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

No branches or pull requests

3 participants