Home > Blockchain >  React: Invalid hook call error using Material UI
React: Invalid hook call error using Material UI

Time:07-14

I am working on a React project created with create-react-app that uses material UI and I am getting these errors:

enter image description here

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:

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

  1. Remove react and react-dom from dependencies in package.json.
  2. Add react and react-dom to peerDependencies in package.json.
  3. Add react and react-dom to devDependencies in package.json.
  4. Add the rewire package to devDependencies in package.json.
  5. 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.
  6. 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.
  7. 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 using npm run build. When using npm 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.
  8. 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'
        }
      ]
    }),
    ...
  • Related