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

Error message / stack trace for Invariant Violation: Element type is invalid is still useless most of the time - possible solution enclosed. #8340

Closed
clarabstract opened this issue Nov 18, 2016 · 4 comments

Comments

@clarabstract
Copy link

Using React v15.3.2 and ReactServer.renderToString but I'm reasonably sure the behaviour is the same with ReactDOM (and I've not seen anything in the release notes of 15.4 regarding improved error messages from render).

The problem is of course that the source of the stack-trace is simply the originating render or renderToString call. In certain occasions the error messages includes the name of a component class whose render() method to check, but even that is not terribly useful for larger components.

I understand there are limitations to how much debug information can tag along element creation code during normal operation, but might it not be practical to simply re-render and re-check (using more heavy-weight tracing) a faulty call to createElement after the error already happened?

Here is a proof of concept (which works great and has been very helpful):

let childContextMap = new WeakMap();
let findInvalidElement = (el, path, ctx) => {
  // console.log('-path', path)
  if (el === null || typeof el === 'undefined' || typeof el === 'boolean' || typeof el === 'string') {
    return;
  }
  if (Array.isArray(el)) {
    for (let [idx, item] of el.entries()) {
      findInvalidElement(item, `${path}[${idx}]`, ctx);
    }
    return;
  }
  if ( !(
      typeof el.type === 'function'
      || typeof el.type === 'string'
    ) ) {
    console.warn('/!\\ INVALID ELEMENT TYPE FOUND /!\\');
    console.warn(`At: ${path}`);
    console.warn('Type:', el.type);
  }

  if (childContextMap.has(el)) {
    ctx = childContextMap.get(el);
  }

  if (typeof el.type === 'function') {
    if (el.type.prototype instanceof React.Component) {
      let element = new (el.type)(el.props, ctx);
      let childContext = {};
      if (typeof element.getChildContext === 'function') {
        childContext = Object.assign({}, ctx,
          element.getChildContext());
        React.Children.forEach(el.props.children, child => {
          childContextMap.set(child, childContext);
        });
      }
      findInvalidElement(element.render(), `${path}${el.type.displayName || el.type.name || el.type}.render()->`, ctx);
    } else {
      findInvalidElement(el.type(el.props, ctx), `${path}${el.type.displayName || el.type.name || el.type}()->`, ctx);
    }
  } else if (el.props && el.props.children) {
    let idx = 0;
    React.Children.forEach(el.props.children, child => {

      findInvalidElement(child, `${path}${idx}:${el.type.name || el.type}/`, ctx);
      idx++;
    });
  }
};

It's just a quick kludged-together element-tree renderer that tracks the 'path' to each individual node in the tree (and is thus able to report it if the type is incorrect). Together with the stack trace this allows for exact identification of the faulty node.

For example, the path:

Main.render()->Provider.render()->IntlProvider.render()->1:div/0:div/ReactCSSTransitionGroup.render()->ReactTransitionGroup.render()->0:span/ReactCSSTransitionGroupChild.render()->0:div/0:div/Connect(Connect(ExampleForm)).render()->Connect(ExampleForm).render()->ExampleForm.render()->3:form/ExampleSubForm.render()->3:div/

Though verbose, it can be followed without ambiguity:

  • (starting where the stack-trace ends)
  • In the render() method of Main
  • In the render() method of Provider
  • In the render() method of IntlProvider
  • In the second (1) child (a div)
  • In the first (0) child (a div)
  • In the render() method of ReactCSSTransitionGroup
  • In the render() method of ReactTransitionGroup
  • In the first (0) child (a span)
  • In the render() method of ReactCSSTransitionGroupChild
  • In the first (0) child (a div)
  • In the first (0) child (a div)
  • ... and so on, you get the idea
@gaearon
Copy link
Collaborator

gaearon commented Nov 18, 2016

Does this help? #7859

@gaearon
Copy link
Collaborator

gaearon commented Oct 4, 2017

We fixed this in #8495.

For best results, use this plugin in development.

@gaearon gaearon closed this as completed Oct 4, 2017
@worc
Copy link

worc commented Nov 2, 2018

the error still doesn't print the correct trace. i don't think it's fixed.

@SeedyROM
Copy link

SeedyROM commented May 2, 2019

This is still true May 2nd 2019.

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

No branches or pull requests

4 participants