admin管理员组

文章数量:1401233

Hi I am trying to create a sample library using React 18.2.0 & Microbundle and Although library built successfully, but when it is consumed in the client app I'm getting the below error in console log:

Library source code

Below is my library code.

App.js

import './App.css';
import Dropdown from "./ponents/Dropdown";

function App() {
   
  let dropdown_data = ['Item 1', 'Item 2', 'Item 3'];

    return (
      <div className="dropdown">
         <Dropdown jsonData={dropdown_data} />
      </div>
  )
}

export default App;

src/ponents/Dropdown.js

import React from "react";
import {useEffect, useState} from 'react';

export const Dropdown = (props) => {
    const [dropdown, setDropdown] = useState([]);
    useEffect(() => {
        loadData();
    }, []);
    const loadData = () => {
        setDropdown(props.jsonData);
    }
    return (
        <div className="dropdown">
            <select> {
                dropdown.map((item, index) => (
                    <option key={index}>
                        {item}</option>
                ))
            } </select>
        </div>
    )

}

src/lib.package.js

export { Dropdown } from "./ponents/Dropdown.js";

package.json

{
  "name": "libtestone",
  "version": "0.1.0",
  "private": true,
  "main": "./dist/lib.umd.js",
    "module": "./dist/lib.module.js",
    "source": "src/lib.package.js",
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.3.0",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "build:lib": "microbundle --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "microbundle": "^0.15.0"
  }
}

Client application source code

This is where I am consuming the package.

App.js

import './App.css';
import {Dropdown} from "libtestone";

function App() {
  return (
    <div>
      <Dropdown />
    </div>
  );
}

export default App;

Hi I am trying to create a sample library using React 18.2.0 & Microbundle and Although library built successfully, but when it is consumed in the client app I'm getting the below error in console log:

Library source code

Below is my library code.

App.js

import './App.css';
import Dropdown from "./ponents/Dropdown";

function App() {
   
  let dropdown_data = ['Item 1', 'Item 2', 'Item 3'];

    return (
      <div className="dropdown">
         <Dropdown jsonData={dropdown_data} />
      </div>
  )
}

export default App;

src/ponents/Dropdown.js

import React from "react";
import {useEffect, useState} from 'react';

export const Dropdown = (props) => {
    const [dropdown, setDropdown] = useState([]);
    useEffect(() => {
        loadData();
    }, []);
    const loadData = () => {
        setDropdown(props.jsonData);
    }
    return (
        <div className="dropdown">
            <select> {
                dropdown.map((item, index) => (
                    <option key={index}>
                        {item}</option>
                ))
            } </select>
        </div>
    )

}

src/lib.package.js

export { Dropdown } from "./ponents/Dropdown.js";

package.json

{
  "name": "libtestone",
  "version": "0.1.0",
  "private": true,
  "main": "./dist/lib.umd.js",
    "module": "./dist/lib.module.js",
    "source": "src/lib.package.js",
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.3.0",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "build:lib": "microbundle --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "microbundle": "^0.15.0"
  }
}

Client application source code

This is where I am consuming the package.

App.js

import './App.css';
import {Dropdown} from "libtestone";

function App() {
  return (
    <div>
      <Dropdown />
    </div>
  );
}

export default App;
Share Improve this question edited Jul 14, 2022 at 0:18 Sergey Vyacheslavovich Brunov 18.1k7 gold badges52 silver badges85 bronze badges asked Jul 11, 2022 at 16:52 MadpopMadpop 7254 gold badges29 silver badges63 bronze badges 20
  • I remember having a similar problem when using Webpack's Module Federation, it turned to be that there was multiple instances of React. – Camilo Commented Jul 13, 2022 at 18:51
  • List react and react-dom as peerDependencies. – morganney Commented Jul 14, 2022 at 0:26
  • @Camilo How did u resolve it ? – Madpop Commented Jul 14, 2022 at 1:50
  • @morganney where in the plugin ? – Madpop Commented Jul 14, 2022 at 1:51
  • 1 @SergeyVyacheslavovichBrunov No Via Local file system only npm i file:../libtestone and below is my repo github./Devsnapper/reac-lib – Madpop Commented Jul 14, 2022 at 2:48
 |  Show 15 more ments

4 Answers 4

Reset to default 4

You should probably put react, react-dom and react-scripts in peerDependencies so that they don't get bundled along with your library. That could be the reason you're facing this issue, since putting react in dependencies might create two instances of React.

Edit: The real culprit was the usage of npm i file:../libone.

Apparently this is an issue where it won't happen if you publish your library, because if react and react-dom are in peerDependencies, it will use the local instance of React.

However in your case, you have a library folder adjacent to a client app folder, and whenever you import your ponent from the library it's using its own instance of React from its node_modules, which is causing the errors.

In order to solve this, make sure to have the following:

  • In libone: package.json:

    "devDependencies": {
      "microbundle": "^0.15.0"
    },
    "peerDependencies": {
      "@testing-library/jest-dom": "^5.16.4",
      "@testing-library/react": "^13.3.0",
      "@testing-library/user-event": "^13.5.0",
      "react": "file:../userone/node_modules/react",
      "react-dom": "file:../userone/node_modules/react-dom",
      "react-scripts": "5.0.1",
      "web-vitals": "^2.1.4"
     }
    

Also change build:lib to this (not sure if it has a big impact but that's the setup I had):

  "build:lib": "microbundle build --globals react=React,react-dom=ReactDOM --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react"
  • In userone: package.json:

    "dependencies": {
    "libone": "file:../libone",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
    }
    

Make sure to remove both node_modules, run npm install in userone, then npm install in libone, then remove dist folder from libone and run npm run build:lib.

Notice that in libone package.json we're referencing the react and react-dom that are installed inside your userone folder, and that solves the problem of two React instances when using npm i file:../libone.

libone

  • Your library (libone/) must not have a hard dependency on react; a peer dependency is enough. (If you'd like it to have a self-contained demo app, then things are slightly different and I might remend Vite's library mode like I've recently done here instead of microbundle).
  • The library should not depend on react-scripts in any way; it doesn't need to.

All in all, this package.json does the trick (the script being named prepare so the dist/ files get built at a correct time; naturally you could still use build:lib, but then your prepare script must call it with e.g. npm run build:lib):

{
  "name": "libone",
  "version": "0.1.0",
  "private": true,
  "source": "lib/index.js",
  "main": "./dist/lib.umd.js",
  "module": "./dist/lib.module.js",
  "peerDependencies": {
    "react": "^18.2.0"
  },
  "scripts": {
    "prepare": "microbundle build --globals react=React,react-dom=ReactDOM --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react"
  },
  "devDependencies": {
    "microbundle": "^0.15.0"
  }
}

userone

  • The client application userone can use react-scripts, vite, or whatever way you like to build a React app.
  • The client application's dependencies should contain React, ReactDOM, etc.

With a layout that has libone/ and userone/ as siblings, using react-scripts, userone's package.json could be something like

{
  "name": "userone",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "libone": "../libone",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test"
  }
}

In addition, if you use npm link or yarn link for libone, it's imperative that libone's node_modules/ directory does not contain React; that will confuse Webpack.

It looks like there are multiple copies of React in the same bundle.

So there are some possible ways to solve this error:

  1. Try to publish your package to NPM and then import it directly from NPM.

  2. A (hacky) workaround at the moment using npm-link-shared and a prestart npm script to essentially replace the one package's react dependency with a symlink to the other's, so they use the same instance.

      "prestart": "npm-link-shared ./node_modules/<other package>/node_modules . react"
    
  3. It can be resolved by adding:

       alias: {
         react: path.resolve('./node_modules/react')
       }
    

to resolve property in webpack config of my main app.

Alternative solution:

As React docs says:

In order for Hooks to work, the react import from your application code needs to resolve to the same module as the react import from inside the react-dom package.

If these react imports resolve to two different exports objects, you will see this warning. This may happen if you accidentally end up with two copies of the react package.

So it looks like you need to check the above statement as you are developing separate library. How can is it possible to check? It is possible to check by:

The First Way:

If you use Node for package management, you can run this check in your project folder:

 npm ls react

If you see more than one React, you’ll need to figure out why this happens and fix your dependency tree. For example, maybe a library you’re using incorrectly specifies react as a dependency (rather than a peer dependency). Until that library is fixed, Yarn resolutions is one possible workaround.

The second way:

You can also try to debug this problem by adding some logs and restarting your development server:

// Add this in node_modules/react-dom/index.js
window.React1 = require('react');

// Add this in your ponent file
require('react-dom');
window.React2 = require('react');
console.log(window.React1 === window.React2);

If it prints false then you might have two Reacts and need to figure out why that happened. This issue includes some mon reasons encountered by the munity.

Introduction

Let's consider the following versions as the current versions:

  • npm: 8.13.2.

Let's consider the article that describes the same problem: Invalid Hook Call Warning – React.

Changes to get reproducible example

Client application (apptestone)

Since the provided (posted right in the question, not on GitHub) source code of the client application is not plete, I have created an example client application with Create React App:

/question-72942020/src$ npx create-react-app apptestone

package.json

{
  "name": "apptestone",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "5.16.4",
    "@testing-library/react": "13.3.0",
    "@testing-library/user-event": "13.5.0",
    "libtestone": "file:../libtestone",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

Resulting directory structure

/question-72942020/src$ tree --charset unicode -L 2
.
|-- apptestone
|   |-- build
|   |-- node_modules
|   |-- package.json
|   |-- package-lock.json
|   |-- public
|   |-- README.md
|   `-- src
`-- libtestone
    |-- dist
    |-- node_modules
    |-- package.json
    |-- package-lock.json
    `-- src

General changes

Library package (libtestone/package.json): Reduce scope of dependencies

Update the file as follows:

"dependencies": {
},

<…>

"peerDependencies": {
  "react": "^18.2.0",
  "react-dom": "^18.2.0"
},

<…>

"devDependencies": {
  "microbundle": "^0.15.0",
  "@testing-library/jest-dom": "^5.16.4",
  "@testing-library/react": "^13.3.0",
  "@testing-library/user-event": "^13.5.0",
  "react-scripts": "5.0.1",
  "web-vitals": "^2.1.4"
},

Run the mand:

/question-72942020/src/libtestone$ npm clean-install

Library ponent (libtestone/src/ponents/Dropdown.js): Use «fallback» value

Update the function as follows:

const loadData = () => {
    setDropdown(props.jsonData || ["Loaded data"]);
};

This will allow using the Dropdown ponent like it is shown in your example:

function App() {
  return (
    <div>
      <Dropdown />
    </div>
  );
}

Analysis

After making the changes described in the «Changes to get reproducible example» and «General changes» sections:

  1. The problem has been reproduced.
  2. The analysis has been started.

It is an important detail that you have installed the library package into the application package from a local file system (not from an npm registry).

It seems that had you installed the library from an npm registry, the changes described in the «General changes» section would be sufficient to solve the problem or would not be necessary at all.

It seems that the «Duplicate React» problem takes place according to the symptom (the output):

/question-72942020/src/apptestone$ npm ls react
[email protected] /question-72942020/src/apptestone
├─┬ @testing-library/[email protected]
│ └── [email protected] deduped
├─┬ [email protected] -> ./../libtestone
│ ├─┬ @testing-library/[email protected]
│ │ └── [email protected] deduped
│ ├─┬ [email protected]
│ │ └── [email protected] deduped
│ ├─┬ [email protected]
│ │ └── [email protected] deduped
│ └── [email protected] <!-- NOTE!
├─┬ [email protected]
│ └── [email protected] deduped
├─┬ [email protected]
│ └── [email protected] deduped
└── [email protected] <!-- NOTE!

Please, note the <!-- NOTE! markers.

The article states:

This problem can also e up when you use npm link or an equivalent. In that case, your bundler might “see” two Reacts — one in application folder and one in your library folder.

Possible solution

The article describes a possible solution:

Assuming myappand mylib are sibling folders, one possible fix is to run npm link ../myapp/node_modules/react from mylib. This should make the library use the application’s React copy.

Let's adapt the solution to the current case.

The resulting mand:

/question-72942020/src/libtestone$ npm link ../apptestone/node_modules/react ../apptestone/node_modules/react-dom

Please, note the working directory path (before $).

Let's check the created symbolic links:

/question-72942020/src/libtestone$ ls -la node_modules/react{,-dom}
<…> node_modules/react -> ../../apptestone/node_modules/react
<…> node_modules/react-dom -> ../../apptestone/node_modules/react-dom

Let's check the duplication:

/question-72942020/src/apptestone$ npm ls react
[email protected] /question-72942020/src/apptestone
├─┬ @testing-library/[email protected]
│ └── [email protected] deduped
├─┬ [email protected] -> ./../libtestone
│ ├─┬ @testing-library/[email protected]
│ │ └── [email protected] deduped -> ./node_modules/react
│ ├─┬ [email protected]
│ │ └── [email protected] deduped -> ./node_modules/react
│ ├─┬ [email protected]
│ │ └── [email protected] deduped -> ./node_modules/react
│ └── [email protected] -> ./node_modules/react <!-- NOTE!
├─┬ [email protected]
│ └── [email protected] deduped
├─┬ [email protected]
│ └── [email protected] deduped
└── [email protected] <!-- NOTE!

Please, note the <!-- NOTE! markers.

This is it.

Development workflow

Now it is possible to work on the library: make a change, build it (npm run build:lib).

Given that the application is started in the development mode (npm start), the library changes will be taken into account (upon library rebuild: npm run build:lib).

Caveat

It seems that every time a package installation npm mand (npm install, etc.) is performed for the library project, the npm links will be reverted to module directories.
It is necessary to recreate them.

本文标签: