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
- Fix PathHoister hoisting before bindings. Fixes #5149 and enables a few additional safe hoists. — committed to STRML/babel by STRML 7 years ago
- Fix PathHoister hoisting before bindings. Fixes #5149 and enables a few additional safe hoists. — committed to STRML/babel by STRML 7 years ago
- Fix PathHoister hoisting before bindings. Fixes #5149 and enables a few additional safe hoists. — committed to STRML/babel by STRML 7 years ago
- Fix PathHoister hoisting before bindings. (#5153) Fixes #5149 and enables a few additional safe hoists. — committed to babel/babel by STRML 7 years ago
- Merge master into 7.0 (#5310) * Add new flow preset (#5288) * Fix PathHoister hoisting JSX member expressions on "this". (#5143) The PathHoister ignored member references on "this", causing it ... — committed to babel/babel by danez 7 years ago
- Fix PathHoister hoisting before bindings. (#5153) Fixes #5149 and enables a few additional safe hoists. — committed to babel/babel by STRML 7 years ago
Please don’t ping random people, that won’t make the resolution of the problem faster.