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

Created on 18 Nov 2016  路  4Comments  路  Source: facebook/react

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

All 4 comments

We fixed this in https://github.com/facebook/react/pull/8495.

For best results, use this plugin in development.

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

This is still true May 2nd 2019.

Was this page helpful?
0 / 5 - 0 ratings