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

Uncaught TypeError: Cannot read property 'dragSource' of undefined #429

Closed
mordrax opened this issue Apr 6, 2016 · 10 comments
Closed

Uncaught TypeError: Cannot read property 'dragSource' of undefined #429

mordrax opened this issue Apr 6, 2016 · 10 comments

Comments

@mordrax
Copy link

mordrax commented Apr 6, 2016

TL;DR - I've worked out what causes this and fixed it, but I've got no idea how to fix the root cause or protect myself from it happening again.

So I've been scratching my head for the last couple of hours on the error above. The code comes straight out of the textbook:

const collect = (connect, monitor) => ({
  connectDragSource: connect.dragSource(), // <-- connect is undefined
  isDragging       : monitor.isDragging()
});

I first thought it might be my redux smart containers or that the DragDropContext was separated from the DragSource by a few layers of dumb components.

What I ended up finding was that one of the props I was passing into the component wrapped by the drag source was incorrect (bug within my app). Instead of passing in a object, I was passing a integer and then trying to get keys from that integer.

So I thought that there must be some place within ReactDnd that is not wrapping client code (my code) and silently eating up errors.
I've tried wrapping my dropTarget function, the spec functions and all functionality that is linked to ReactDnd but I still get the above error.

The only way that's worked is to fix the bug within my app and that made the error go away.

What really bugs me is that ReactDnd failed silently, and I had no idea why connect is undefined.

@mordrax
Copy link
Author

mordrax commented Apr 11, 2016

So I'm getting this again, really annoying to debug, this is the stacktrace that I see:

image

So in DragDropContainer, it tries to get the current state:

    function DragDropContainer(props, context) {                                                                       // 89
      _classCallCheck(this, DragDropContainer);                                                                        // 90
                                                                                                                       // 91
      _Component.call(this, props, context);                                                                           // 92
      this.handleChange = this.handleChange.bind(this);                                                                // 93
      this.handleChildRef = this.handleChildRef.bind(this);                                                            // 94
                                                                                                                       // 95
      _invariant2['default'](typeof this.context.dragDropManager === 'object', 'Could not find the drag and drop manager in the context of %s. ' + 'Make sure to wrap the top-level component of your app with DragDropContext. ' + 'Read more: http://gaearon.github.io/react-dnd/docs-troubleshooting.html#could-not-find-the-drag-and-drop-manager-in-the-context', displayName, displayName);
                                                                                                                       // 97
      this.manager = this.context.dragDropManager;                                                                     // 98
      this.handlerMonitor = createMonitor(this.manager);                                                               // 99
      this.handler = createHandler(this.handlerMonitor);                                                               // 100
      this.disposable = new _disposables.SerialDisposable();                                                           // 101
                                                                                                                       // 102
      this.receiveProps(props);                                                                                        // 103
      this.state = this.getCurrentState();                                                                             // 104
    }                                      

In there, it tries to get the next state via connect, however this.handlerConnector is undefined at this point and causes the error can't read dragSource of undefined.

    DragDropContainer.prototype.getCurrentState = function getCurrentState() {                                         // 173
      var nextState = collect(this.handlerConnector, this.handlerMonitor);                                             // 174
      if (process.env.NODE_ENV !== 'production') {                                                                     // 175
        _invariant2['default'](_lodashIsPlainObject2['default'](nextState), 'Expected `collect` specified as the second argument to ' + '%s for %s to return a plain object of props to inject. ' + 'Instead, received %s.', containerDisplayName, displayName, nextState);
      }                                                                                                                // 177
      return nextState;                                                                                                // 178
    }; 

@mordrax
Copy link
Author

mordrax commented Apr 11, 2016

I've found my root cause this time.

My drag source looks like this:

export const dragTargets = props => {
  return props.item.base.type; // if props.item.base.type is undefined, then you get the error in the title
};
// DragSource.js
if (typeof type !== 'function') {
    invariant(
      isValidType(type),
      'Expected "type" provided as the first argument to DragSource to be ' +
      'a string, or a function that returns a string given the current props. ' +
      'Instead, received %s. ' +
      'Read more: http://gaearon.github.io/react-dnd/docs-drag-source.html',
      type
    );
    getType = () => type;
  }

If the drag type passed into DragSource is not a function, then it actually does a isValidType check.
However, if the type is a function, it just saves it as getType and then calls it here

//decorateHandler.js
    receiveProps(props) {
      this.handler.receiveProps(props);
      this.receiveType(getType(props));
    }

but I tried following this through (got lost in registerSource.js: registry.addSource) and didn't see any obvious places where it does a isValidType check.

So my guess is that if I'm using a function to get the dragType then it doesn't check the type and throws the cryptic error.

@froatsnook
Copy link
Collaborator

Are you sure that props.item.base.type isn't null?

In src/decorateHandler's receiveType, it's exiting early if type === this.currentType (which defaults to null, hence my question) and otherwise calling registerHandler. This calls registerSource which calls react-dnd's registry.addSource which calls validateType, which would error for type=undefined.

function validateType(type, allowArray) {
  if (allowArray && isArray(type)) {
    type.forEach(t => validateType(t, false));
    return;
  }

  invariant(
    typeof type === 'string' || typeof type === 'symbol',
    allowArray ?
      'Type can only be a string, a symbol, or an array of either.' :
      'Type can only be a string or a symbol.'
  );
}

froatsnook added a commit to froatsnook/react-dnd that referenced this issue Apr 11, 2016
When the user provides the DragSource/DropTarget type as a string or
array, the type is checked immediately.  When the user provides the
type as a function, it's not checked until it's added to the registry.
If this function returns null (and the type has not yet been set), then
the type will never be checked, resulting in cryptic error messages.
@mordrax
Copy link
Author

mordrax commented Apr 11, 2016

It is actually undefined because I was refering to props.item.type which doesn't exist, props.item.base.type is the correct one.

But it never picks up the fact it's undefined, until the call to collect(this.handlerConnector, this.handlerMonitor), it calls my collect function and errs out.

Actually, I got stuck with registry.addSource, couldn't find any mention of any other addSource function in the repo... how do you know addSource calls validateType? My whole hypothesis is that it doesn't and that's why I don't get any forewarnings like I would if I'd just pass in a null for dragTargetType.

@froatsnook
Copy link
Collaborator

Hey @mordrax,

react-dnd depends on dnd-core. Check out https://github.com/gaearon/dnd-core/blob/master/src/HandlerRegistry.js#L72

I wrote some code that I thought would fix help if the function returned null, but I'll dig in a bit more later :).

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

kesne commented Aug 20, 2016

@froatsnook Did you ever dive into this? Otherwise it seems like this might be resolved? I haven't seen other bug reports so I'm inclined to believe it is.

@kesne kesne added needs info and removed triage labels Aug 20, 2016
@mordrax
Copy link
Author

mordrax commented Aug 20, 2016

@froatsnook sorry I didn't see this earlier... I've actually moved my project to Elm and wrote my own drag/drop in much the same way that react dnd does it and have had no problems since.

@kesne
Copy link
Collaborator

kesne commented Aug 20, 2016

That's awesome! Glad that you managed to get a solution working (elm is awesome from what I hear!)

I'm going to close this out because it seems as though the issue has been resolved. Thanks for reporting it!

@kesne kesne closed this as completed Aug 20, 2016
@mordrax
Copy link
Author

mordrax commented Aug 20, 2016

@kesne 👍
I've spent time with angular, blaze, react, and whilst react/redux was the best of the lot, Elm is in a class of it's own. You should definitely check it out.

@kesne
Copy link
Collaborator

kesne commented Aug 20, 2016

Well now I know what I'm using on my next project!

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

3 participants