Unable to render useState, useEffect hooks in custom component package using microbundle & React
Asked Answered
P

4

5

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:

Error

Library source code

Below is my library code.

App.js

import './App.css';
import Dropdown from "./components/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/components/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 "./components/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;
Placeeda answered 11/7, 2022 at 16:52 Comment(20)
I remember having a similar problem when using Webpack's Module Federation, it turned to be that there was multiple instances of React.Cofield
List react and react-dom as peerDependencies.Overside
@Cofield How did u resolve it ?Placeeda
@Overside where in the plugin ?Placeeda
Dear @Madpop, Could you please specify how have you installed the libtestone package into the client application package? For example, have you installed it from an npm registry (npm install libtestone) or from a local file system (npm install path/to/libtestone)?Harwin
@SergeyVyacheslavovichBrunov No Via Local file system only npm i file:../libtestone and below is my repo github.com/Devsnapper/reac-libPlaceeda
@Overside Tried this in both app as well as in lib also it didnt work outPlaceeda
Please update the repo :)Allieallied
@Placeeda Please include your files in the repository proper, not in a .7z file.Spue
@AymenHammami Content also matchesPlaceeda
@Spue U can extract the 7z file both the consumer and app library is there in zip filePlaceeda
@Placeeda I know I could, but I really don't have the inclination to download a 7z archiver, download the file, extract it and look at the files, when they could just be in the repository.Spue
@Spue github.com/Devsnapper/React-Package please check nowPlaceeda
@Placeeda You've added two submodules that point nowhere.Spue
@Placeeda did you try deleting node_modules and .lock file from client side and changing build:lib like in my edited answer?Faintheart
@AymenHammami im working on thatPlaceeda
@Spue Yes if u clone u will the get the folders and that's all i can do it for nowPlaceeda
@Placeeda Cloning the repository with --recursive gives me two empty folders.Spue
@Spue can u check this url drive.google.com/drive/folders/…Placeeda
@Placeeda I don't remember exactly and I don't longer have access to the repo, but there was some setting in the Module Federation config that allowed you to list shared dependencies or something like that.Cofield
S
1

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 recommend 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.

Spue answered 14/7, 2022 at 11:59 Comment(5)
I tried the above approach and removed node_mod and package lock json and did a npm install after doing these changes still im getting the same invalid hook and can upload the repo which u worked.Placeeda
Please see github.com/akx/so72942020 ...Spue
Glad you got it to work. It might be a npm/yarn cache issue on the non-working machine, then.Spue
a small thing how can i embedded the css ?Placeeda
Different libraries do that in different ways. You might want to consider styled-components/emotion/... or just tell your users to import 'libone/dist/foo.css'; in their app.Spue
F
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 component 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.

Faintheart answered 14/7, 2022 at 0:26 Comment(13)
where should we add this in consumer app or plugin ? here is my git repo github.com/Devsnapper/reac-libPlaceeda
Tried this in both app as well as in lib also it didnt work out.Placeeda
You should add it to your plugin/lib. Make sure that you removed react, react-dom and react-scripts from dependencies and moved them to peerDependencies. Also make sure to delete node_modules and the .lock file when trying it with the client AppFaintheart
If that doesn't work, I added another possible solution in answer, hope it can help.Faintheart
it didnt work and i changed the build :lib command tooPlaceeda
@Placeeda I saw you pushed node_modules in github in the old repo, you don't want to do that. Did you delete the node_modules from client side and installed fresh after changing build:lib? Did you change the import to the newly created lib? Did any error change? Or are you getting same error?Faintheart
its same error and by mistake node mod went and after changing build:lib also same and yes i changed to the new created lib and getting same errorPlaceeda
Let us continue this discussion in chat.Placeeda
@Placeeda I think I found the issue, it's related to using file:../ since if you publish the lib while it's using react as peerDependency it's going to use the React instance of that current project. This could help: #56021612Faintheart
@ Aymen Hammami is it running for u ?Placeeda
@Placeeda yes just solved it, will update answer shortlyFaintheart
is it possible to update the folder in drive ?Placeeda
@Placeeda I just updated the answer. Make sure to follow the steps and if does not work let me know I might have forgotten a step or two, not that easy to remember all the changes I've tried. It's currently running for me. imgur.com/a/1wxguftFaintheart
H
1

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 complete, 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 command:

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

Library component (libtestone/src/components/Dropdown.js): Use «fallback» value

Update the function as follows:

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

This will allow using the Dropdown component 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 come 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 command:

/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 command (npm install, etc.) is performed for the library project, the npm links will be reverted to module directories.
It is necessary to recreate them.

Harwin answered 14/7, 2022 at 4:43 Comment(1)
I think the issue really is just that the library has bundled react instead of using it as a dependency, which is a no-no.Spue
S
1

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 component 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 common reasons encountered by the community.

Sizable answered 14/7, 2022 at 5:2 Comment(10)
i found some packages when i ran npm ls react and result i saw was deduplicated and im not sure how to remove thosePlaceeda
@Placeeda I am sorry, however, could you elaborate what you mean? Have you tried the first three solutions from my reply?Sizable
I followed the Alternative way 1. npm ls react and i found some deduped react versions and i cannot push the plugin to npm as i have to use internally only and the versions in both consumer and as well as in lib are samePlaceeda
@Placeeda do you use Webpack?Sizable
using microbundle and tried roll up also below is my rep github.com/Devsnapper/reac-libPlaceeda
@Placeeda I suggest you to try the second and third ways and see whether it helped to youSizable
@Placeeda do you have package.json for your library where dropdown component is implementedSizable
Yes in question it self i posted it under src/lib.package.jsPlaceeda
@Placeeda and what dependencies does it have?Sizable
Let us continue this discussion in chat.Placeeda
S
1

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 recommend 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.

Spue answered 14/7, 2022 at 11:59 Comment(5)
I tried the above approach and removed node_mod and package lock json and did a npm install after doing these changes still im getting the same invalid hook and can upload the repo which u worked.Placeeda
Please see github.com/akx/so72942020 ...Spue
Glad you got it to work. It might be a npm/yarn cache issue on the non-working machine, then.Spue
a small thing how can i embedded the css ?Placeeda
Different libraries do that in different ways. You might want to consider styled-components/emotion/... or just tell your users to import 'libone/dist/foo.css'; in their app.Spue

© 2022 - 2025 — McMap. All rights reserved.