I am working on a React project created with create-react-app that uses material UI and I am getting these errors:
as you can see it is something internal to material UI at the moment I set a custom theme:
import { ThemeProvider } from "@mui/material/styles";
import { CssBaseline } from "@mui/material";
import { CacheProvider } from "@emotion/react";
import { createEmotionCache } from "./utils/create-emotion-cache";
import { theme } from "./theme";
import SiteRoutes from "./routes";
import { createTheme } from "@mui/material";
export const theme = createTheme({...})
const clientSideEmotionCache = createEmotionCache();
function App(props) {
const { emotionCache = clientSideEmotionCache } = props;
return (
<>
<ThemeProvider theme={theme}>
<CacheProvider value={emotionCache}>
<CssBaseline />
<SiteRoutes />
</CacheProvider>
</ThemeProvider>
</>
);
}
export default App;
I found some solutions but none of them work except for one: delete react folder from node_modules. It seems that there are multiple imports for react as described here: https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react
my package.json looks like this:
{
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"dependencies": {
"@emotion/cache": "^11.9.3",
"@emotion/react": "^11.9.3",
"@emotion/server": "^11.4.0",
"@emotion/styled": "^11.9.3",
"@mui/icons-material": "^5.8.4",
"@mui/lab": "^5.0.0-alpha.89",
"@mui/material": "^5.8.7",
"@mui/styles": "^5.8.7",
"chart.js": "^3.8.0",
"date-fns": "^2.28.0",
"formik": "^2.2.9",
"history": "^5.3.0",
"nprogress": "^0.2.0",
"prop-types": "^15.8.1",
"react-chartjs-2": "^4.2.0",
"react-helmet": "^6.1.0",
"react-perfect-scrollbar": "^1.5.8",
"react-router-dom": "^6.3.0",
"react-scripts": "^5.0.1",
"uuid": "^8.3.2",
"yup": "^0.32.11"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"devDependencies": {}
}
When executing npm ls react
:
├─┬ @emotion/[email protected]
│ └── [email protected] deduped
├─┬ @emotion/[email protected]
│ └── [email protected] deduped
├─┬ @mui/[email protected]
│ └── [email protected] deduped
├─┬ @mui/[email protected]
│ ├─┬ @mui/[email protected]
│ │ └── [email protected] deduped
│ ├─┬ @mui/[email protected]
│ │ ├─┬ @mui/[email protected]
│ │ │ └── [email protected] deduped
│ │ └── [email protected] deduped
│ ├─┬ @mui/[email protected]
│ │ └── [email protected] deduped
│ ├─┬ @mui/[email protected]
│ │ └── [email protected] deduped
│ ├─┬ [email protected]
│ │ └── [email protected] deduped
│ ├── [email protected] deduped
│ └─┬ [email protected]
│ └── [email protected] deduped
├─┬ @mui/[email protected]
│ └── [email protected] deduped
├─┬ @mui/[email protected]
│ ├─┬ @mui/[email protected]
│ │ └── [email protected] deduped
│ └── [email protected] deduped
├─┬ [email protected]
│ └── [email protected] deduped
├─┬ [email protected]
│ └── [email protected] deduped
├─┬ [email protected]
│ └── [email protected] deduped
├─┬ [email protected]
│ ├─┬ [email protected]
│ │ └── [email protected] deduped
│ └── [email protected] deduped
├─┬ [email protected]
│ └── [email protected] deduped
├─┬ [email protected]
│ ├─┬ [email protected]
│ │ └── [email protected] deduped
│ └── [email protected] deduped
├─┬ [email protected]
│ └── [email protected] deduped
└── [email protected]
Solutions tried:
- Delete react folder from node_modules: not ideal since this project is being worked with a team and will be deployed to aws.
- https://github.com/facebook/react/issues/13991#issuecomment-496383268: Did not work at all, import error, can only import from src folder.
- https://github.com/facebook/react/issues/13991#issuecomment-449597362: Did not work at all, added references to package.json,
yarn install
yarn start
and the same thing.
How to reproduce: https://github.com/goto3/React-Material-Admin-Dashboard I created a repo so that you can test this. Just execute npm i --force
and npm start
.
CodePudding user response:
I also experienced the Invalid hook call error with a recent project using React v18.2.0 and Material v5.8.7. However, I wasn't using create-react-app.
The steps listed below should solve your problem. The basic idea is to remove the possibility of multiple versions of React in your package by removing React from your production bundle, and having your package consumers take responsibility for including React. There are side-effects though: 1. Your package consumers need to know they must provide React in their environment, and 2. they must copy your media assets into their worktree or build directory.
I updated my local copy of your project with the changes below. It was tested by importing the compiled bundle into another project. Everything worked fine.
Steps
- Remove react and react-dom from
dependencies
in package.json. - Add react and react-dom to
peerDependencies
in package.json. - Add react and react-dom to
devDependencies
in package.json. - Add the rewire package to
devDependencies
in package.json. - Use the
externals
Webpack configuration option. This prevents Webpack from including React in your (production) output bundle. Excluding React from your bundle means that the project that consumes your package (e.g. a file that uses:import DASHBOARD from 'react-material-admin-dashboard';
) must include React in its own environment. - Use the
output.library
Webpack configuration option to configure how your package will be exposed to the project that consumes it. A project consuming your package could use one of several ways to expose your bundle to itself. For example, using CommonJS:var DASHBOARD = require('react-material-admin-dashboard');
. Or using ECMAScript 2015 (ES6):import DASHBOARD from 'react-material-admin-dashboard';
. This configuration option can be used to offer a range of possibilities. - Test your bundle as an imported package. To do so you need to edit your
react-material-admin-dashboard
entry point (index.js) to export a component (for import by the consuming project) instead of injecting the component directly into the DOM (the DOM element with id="root"). You would do this when usingnpm run build
. When usingnpm start
you need your entry point to inject the component directly into the DOM. (You could try creating two entry points and having the Webpack configuration programmatically select which one to use (export or injection) based on whether it is operating in development mode or production mode. - Provide instructions for your package consumers about: React (they must include it in their bundle or provide it as a global), and asset management (they have to copy your media assets to their worktree or build directory).
Webpack configuration
WebPack configuration is not exposed when using Create-React-App. But, you can use a library like rewire to edit the Webpack configuration without ejecting create-react-app.
Steps 1 through 4 should be simple enough. Below, are details for the remaining steps. Inspiration came from an example on this blog post Overriding the Create-React-App Webpack Configuration Without Ejecting:
Steps 5 and 6 details:
Create build.js file
Create a build.js file to override the Webpack configuration. I placed this file in: <root>/scripts/build.js. This file adds the externals and output.library options:
// build.js
const YOUR_PACKAGE_NAME = 'react-material-admin-dashboard';
const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
const config = defaults.__get__('config');
// Configure the library name and how it will be exposed.
const libraryOptions = {
name: YOUR_PACKAGE_NAME,
type: 'umd',
umdNamedDefine: true
};
config.output = {
...config.output,
/* Add static value of 'index.js' for a stable filename for import into another project. */
/* Create-React-App uses the dynamic [contenthash] placeholder value for the filename. */
/* filename must match 'main' property in package.json (after the 'build/' directory) */
filename: 'static/js/index.js',
library: libraryOptions
};
// Prevent Webpack from bundling react and react-dom in production builds.
config.externals = {
react: {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react',
umd: 'react'
},
'react-dom': {
root: 'ReactDOM',
commonjs2: 'react-dom',
commonjs: 'react-dom',
amd: 'react-dom',
umd: 'react-dom'
}
};
Add main
property to package.json
Include a main
property in package.json to indicate the location of the entry point. E.g.:
{
"name": "react-material-admin-dashboard",
"author": "goto3",
"licence": "MIT",
"version": "1.0",
"private": false,
"main": "build/static/js/index.js",
...
}
Update build script in package.json
Update the build script in package.json to use the new build.js instead of react-script's build command:
"scripts": {
"start": "react-scripts start",
- "build": "react-scripts build",
"build": "node ./build.js",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
Update index.js entry point to export the component
--- a/src/index.js
b/src/index.js
@@ -1,8 1,9 @@
import { Head } from "./head";
import React from "react";
-import ReactDOM from "react-dom/client";
// import ReactDOM from "react-dom/client";
import App from "./App";
/*
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<>
@@ -10,3 11,13 @@ root.render(
<App />
</>
);
*/
export default function Dashboard() {
return (
<>
<Head />
<App />
</>
);
}
Build your output bundle
With the new settings in place you can call npm install
to install rewire. Then run npm run build
to compile a new bundle for testing in another package. Remember, the index.js entry point needs to inject the component into the 'root' DOM element if you wish to use npm start
.
Step 7 details
To test the new bundle in your local development environment, you can use npm link
in the root directory of the react-material-admin-dashboard
project, followed by using npm link react-material-admin-dashboard
in the root directory of the test project. This links react-material-admin-dashboard into the node_modules folder of the test project as if it were installed using npm install react-material-admin-dashboard
.
The test project's Webpack configuration can be set to copy your media assets into the build directory (which might be called dist) using Copy-Webpack-Plugin. For example:
var CopyWebpackPlugin = require( 'copy-webpack-plugin' );
...
module.exports = {
...
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: './{,**/}*.*',
to: './images',
context: './node_modules/react-material-admin-dashboard/public/images'
}
]
}),
...