when in the development environment, my app works just fine. When in the production environment it crashes with the error:
Uncaught TypeError: (0 , _react.useEffect) is not a function
It happens in a file I created where I import React and useEffect like so:
import React, { useEffect } from 'react' const X = () => { useEffect(() => { ... }) ... }
adding a console.log just below this line confirms that useEffect is indeed undefined when in production and the expected function when in dev.
I checked my package.json, yarn.lock & node_modules for any react or react-dom version that might be under 16.8.0 where useEffect was introduced. But everything is 16.13.1 and they are the main dependency and I did try to clean my yarn cache, delete node_modules & yarn.lock, and re-install.
I tried adding and removing it from peerDependencies
without success.
I put in a check to make sure there are not 2 separate versions of React running, but saving window.React1 = React
inside the library and window.React2 = React
inside my application and checking
window.React1 === window.React2
it was true, so that’s not it either.
Lastly, I also tried to alias React to the specific one in node_modules, but without any luck.
The only solution I’ve found that works are if I import it like so:
import React from 'react'; const X = () => { React.useEffect(() => { ... }) ... }
But this should be exactly the same as using a destructured import?
If I do explicitly use React.useEffect it also forces me to change all of my other useState and useEffect hooks to React.useSate
and React.useEffect
The next error just becomes: TypeError: (0 , _react.useState) is not a function
in another file where I use React hooks.
I want to solve the problem not implement a workaround.
I use microbundle
to bundle my library using React.
I use parcel-bundler
to import the React-component and render it in a dev environment (directly from src) or prod (the bundled library)
The bundled version I use is bundled with .mjs
I checked the output of the minified .mjs bundle as well and inside React is imported like this:
import ue,{useEffect as pe,useState as fe}from"react";
Which looks fine to me.
What I really don’t understand is how a restructured import would break it, but just doing React.useEffect would work just fine?
Here’s my package.json
{ "name": "xxx", "version": "1.1.4", "repository": "git@github.com:xxx/xxx.git", "author": "xxx", "license": "MIT", "source": "src/index.ts", "main": "dist/bundle.js", "umd:main": "dist/bundle.umd.js", "module": "dist/bundle.mjs", "publishConfig": { "registry": "https://npm.pkg.github.com/@xxx" }, "scripts": { "build": "microbundle", "dev": "parcel ./test-app/dev/index.html --port 3000", "start": "parcel ./test-app/serve/index.html --port 3000", "storybook": "start-storybook -s ./public -c .storybook --ci", "prepublishOnly": "yarn build" }, "dependencies": { "@api-platform/admin": "2.1.0", "@api-platform/api-doc-parser": "0.8.2", "@fortawesome/fontawesome-svg-core": "^1.2.28", "@fortawesome/free-solid-svg-icons": "^5.13.0", "@fortawesome/react-fontawesome": "^0.1.9", "@material-ui/core": "^4.9.10", "@material-ui/icons": "^4.9.1", "@react-keycloak/web": "^2.1.1", "@types/pluralize": "^0.0.29", "google-geocoder": "0.2.1", "history": "^4.10.1", "keycloak-js": "^9.0.3", "lodash.debounce": "^4.0.8", "lodash.omit": "^4.5.0", "lodash.set": "4.3.2", "notistack": "0.9.9", "papaparse": "^5.2.0", "parcel-bundler": "1.12.4", "polished": "^3.5.2", "react": "16.13.1", "react-admin": "3.4.1", "react-dom": "16.13.1", "react-is": "16.13.1", "react-redux": "^7.2.0", "recompose": "^0.30.0", "redux": "4.0.5", "styled-components": "5.1.0" }, "devDependencies": { "@babel/core": "7.9.0", "@babel/plugin-syntax-export-default-from": "7.8.3", "@babel/preset-env": "7.9.5", "@babel/preset-react": "7.9.4", "@storybook/addon-a11y": "5.3.18", "@storybook/addon-actions": "5.3.18", "@storybook/addon-info": "5.3.18", "@storybook/addon-knobs": "5.3.18", "@storybook/addon-links": "5.3.18", "@storybook/addon-storyshots": "5.3.18", "@storybook/addon-storysource": "5.3.18", "@storybook/addon-viewport": "5.3.18", "@storybook/react": "5.3.18", "@testing-library/react": "^10.0.3", "@types/jsonld": "1.5.1", "@types/lodash": "4.14.149", "@types/node": "13.11.1", "@types/papaparse": "5.0.3", "@types/react-redux": "7.1.7", "@types/recompose": "^0.30.7", "@types/styled-components": "5.1.0", "@welldone-software/why-did-you-render": "4.0.7", "awesome-typescript-loader": "5.2.1", "babel-loader": "^8.1.0", "babel-plugin-module-resolver": "4.0.0", "babel-plugin-styled-components": "1.10.7", "lodash.get": "4.4.2", "lodash.uniq": "4.5.0", "microbundle": "0.11.0", "openapi-types": "1.3.5", "parcel-plugin-static-files-copy": "2.3.1", "pluralize": "^8.0.0" }, "alias": { "jsonld": "./node_modules/jsonld/dist/jsonld.js" }, "staticFiles": { "staticPath": "public", "watcherGlob": "**" } }
Also worth noting, it’s only React I’m having this problem with. All my other restructured imports work just fine.
Advertisement
Answer
It seem that microbundler
does not tolerate to React. This one create bundle that attempt to use react
from global scope, instead React
that really exposed.
For the same reason your workaround with React.useEffect
works as expected, just imagine that it looks like window.React.useEffect
.
Here is an example of a primitive application:
import ReactDOM from 'react-dom'; import React, { useEffect, useState } from 'react'; /** * necessary workaround, microbundle use `h` pragma by default, * that undefined when use React * another option is to make build with option --jsx * @example microbundle --globals react=React --jsx React.createElement * yes, yet another workaround */ window.h = React.createElement; const X = () => { const [A, B] = useState('world'); useEffect(() => { B('MLyck'); }, []) return `Hello ${A}`; } ReactDOM.render(<X />, document.querySelector('react-app'));
After bundling with just microbundle
it completely broken, but when you try to bundle with
microbundle --globals react=React
as correctly suggest @Jee Mok, it will produce correct bundle. I hope comments will explain what happened.
!function (e, t) { "object" == typeof exports && "undefined" != typeof module ? t(require("react-dom"), require("react")) : "function" == typeof define && define.amd ? define(["react-dom", "react"], t) : t(e.ReactDOM, e.React); /* String above is core of problem, in case you try to bundle without options `--globals react=React` it will looks like: `t(e.ReactDOM, e.react);` Obviously `react` is not defined in `e` e.g. `this` e.g. `window` due to react expose self as `React` */ }(this, function (e, t) { e = e && e.hasOwnProperty("default") ? e.default : e, window.h = ("default" in t ? t.default : t).createElement, e.render(h(function () { var e = t.useState("world"), n = e[0], r = e[1]; return t.useEffect(function () { r("MLyck"); }, []), "Hello " + n; }, null), document.querySelector("react-app")); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.development.js"></script> <react-app></react-app>
And, by the way, “restructured import ” not at all to blame.