babel: transform-react-constant-elements => Element type is invalid

After enabling this Babel react optimization I am getting the following error:

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it’s defined in.

If I disable just this plugin it works fine. My current test setup is:

Input Code

import React from "react";

const HOC = component => component;

const Parent = ({}) => (
  <div className="parent">
    <Child/>
  </div>
);

export default Parent;

let Child = () => (
  <div className="child">
    ChildTextContent
  </div>
);
Child = HOC(Child);

Babel Configuration (.babelrc, package.json, cli command)

{
  "presets": [
    "react-app",
    "stage-0"
  ],
  "env": {
    "development": {
      "plugins": [
        "transform-react-constant-elements",
        "transform-react-inline-elements",
        "transform-react-remove-prop-types",
        "transform-react-pure-class-to-function"
      ]
    }
  },
  "sourceMaps": false
}
{
    "babel-core": "6.21.0",
    "babel-loader": "6.2.10",
    "babel-plugin-transform-react-constant-elements": "^6.9.1",
    "babel-plugin-transform-react-inline-elements": "^6.8.0",
    "babel-plugin-transform-react-pure-class-to-function": "^1.0.1",
    "babel-plugin-transform-react-remove-prop-types": "^0.2.11",
    "babel-preset-react-app": "2.0.1",
    "babel-preset-stage-0": "6.16.0"
}

Expected Behavior

It should probably work out of the box, eventually not optimizing things if they are too complicated to optimize, but should not require the user to rewrite his own code (IMO)

Current Behavior

The compiled output looks like that:

	var HOC = function HOC(component) {
	  return component;
	};

	var _ref2 = _jsx("div", {
	  className: "parent"
	}, void 0, _jsx(Child, {}));

	var Parent = function Parent(_ref) {
	  _objectDestructuringEmpty(_ref);

	  return _ref2;
	};

	exports.default = Parent;

	var _ref3 = _jsx("div", {
	  className: "child"
	}, void 0, "ChildTextContent");

	var Child = function Child() {
	  return _ref3;
	};
	Child = HOC(Child);

You can see that _ref2 is trying to use the Child component, which is declared after the _ref2 creation.

This is due to the HOC usage on the Child component, because comenting the HOC usage produces the following code that works fine:

	var HOC = function HOC(component) {
	  return component;
	};

	var Parent = function Parent(_ref) {
	  _objectDestructuringEmpty(_ref);

	  return _jsx("div", {
	    className: "parent"
	  }, void 0, _jsx(Child, {}));
	};

	exports.default = Parent;

	var _ref2 = _jsx("div", {
	  className: "child"
	}, void 0, "ChildTextContent");

	var Child = function Child() {
	  return _ref2;
	};
	//Child = HOC(Child);

Unfortunatly I’d like to use an HOC on the child. I think the plugin should support this usecase correctly to avoid users to have to rewrite any code. On large codebase this rewrite would be painful for users, and the React error is not helpful as-is: you have to introspect the stacktrace to find the source of the problem.

Possible Solution

The plugin could bypass the optimization in this usecase, exactly like it’s done for when the HOC is used.

I think it makes sense to not optimize in this usecase because the plugin already doesn’t optimize when using Child component without the HOC (probably because it’s not safe), so I don’t see why it would be safer once used through a HOC.

In case this would be safe to optimize, another possible solution is that instead of using static references, the plugin could output memoized functions that return the constant element, so that the constant element initialisation is deferred after app construction.

The output code could look like

       var _ref2 = memoize(function() {
         return _jsx("div", {
	   className: "parent"
	 }, void 0, _jsx(Child, {}));
       }

	var Parent = function Parent(_ref) {
	  _objectDestructuringEmpty(_ref);

	  return _ref2();
	};

As a side note, I would say that generalizing this memorisation technique in the plugin may lead to an app that could be faster on initial load, because only the required constant elements would be constructed on startup. But there would be a performance penalty after initial load due to using a memoized function call. This may be better or not according to the usecase (probably better in large apps that are not using code-splitting) and could be made a plugin option.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 1
  • Comments: 17 (8 by maintainers)

Commits related to this issue

Most upvoted comments

Please don’t ping random people, that won’t make the resolution of the problem faster.