Home > OS >  How to handle duplicate packages from NPM package?
How to handle duplicate packages from NPM package?

Time:03-11

I have an NPM package I am working on which has a dependency of react. I then have a test app which has react installed as a dependency. When I import my npm package into the test app, I get the following error:

Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app

Running npm ls react in my test app suggests I might have a duplicate of react:

[email protected] 
├─┬ @package-name/[email protected] -> ./../package-name-react
│ ├─┬ [email protected]
│ │ └── [email protected] deduped
│ └── [email protected] // <----------------
├─┬ [email protected]
│ ├── [email protected] deduped
│ ├─┬ [email protected]
│ │ └── [email protected] deduped
│ └─┬ [email protected]
│   └── [email protected] deduped
├─┬ [email protected]
│ └── [email protected] deduped
└── [email protected] // <----------------

My package.json for my npm package looks like this:

{
  "name": "@package-name/react",
  "version": "1.0.0",
  "description": "",
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js",
  "files": [
    "dist"
  ],
  "types": "dist/index.d.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "clean": "rimraf dist",
    "build": "npm run clean && rollup -c"
  },
  "devDependencies": {
    "@rollup/plugin-commonjs": "^20.0.0",
    "@rollup/plugin-node-resolve": "^13.0.4",
    "@rollup/plugin-typescript": "^8.2.5",
    "rimraf": "^3.0.2",
    "rollup": "^2.56.2",
    "rollup-plugin-dts": "^3.0.2",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "rollup-plugin-postcss": "^4.0.1",
    "rollup-plugin-terser": "^7.0.2",
    "typescript": "^4.3.5"
  },
  "dependencies": {
    "socket.io-client": "^4.4.1"
  },
  "peerDependencies": {
    "react": "17.0.2",
    "react-dom": "17.0.2"
  }
}

When I remove react and react-dom from peerDependencies, the error goes away but causes other issues. It's almost like peerDependencies are being installed and rolled up in my package.

My component in my package is very simple at this stage and is like so:

const MyComponent  = ({ 
  children 
}) => {
  const [myValue, setValue] = useState(false);

  useEffect(() => {
    setFlagValue(true)
  }, []);

  return (
    <>
      {children}
    </>
  )
};

I am then consuming this package in my test app like so:

import { MyComponent } from "package-name/react";

const MyApp = () => {
  <MyComponent>
    <div>Hello world</div>
  </MyComponent>
}

Rollup config:

import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
import external from 'rollup-plugin-peer-deps-external';
import postcss from 'rollup-plugin-postcss';
import dts from 'rollup-plugin-dts';

const packageJson = require('./package.json');

export default [
    {
        input: 'src/index.ts',
        output: [
            {
                file: packageJson.main,
                format: 'cjs',
                sourcemap: true,
                name: 'react-ts-lib'
            },
            {
                file: packageJson.module,
                format: 'esm',
                sourcemap: true
            }
        ],
        plugins: [
            external(),
            resolve(),
            commonjs(),
            typescript({ tsconfig: './tsconfig.json' }),
            postcss(),
            terser()
        ],
    },
    {
        input: 'dist/esm/types/index.d.ts',
        output: [{ file: 'dist/index.d.ts', format: "esm" }],
        external: [/\.css$/],
        plugins: [dts()],
    },
]

CodePudding user response:

It was not clear from the question description, but looking at the repo, I see that the package is installed locally.

"dependencies": {
  "next": "12.1.0",
  "react": "17.0.2",
  "react-dom": "17.0.2",
  "react-ts-lib": "file:../react-ts-lib"
},

Which means that the lib code still resolves react using its own node_modules (installed locally) rather than the app dependencies.

One way to fix this issue could be to setup the lib project with something like create-react-library, which addresses this problem explicitly:

If you use react-hooks in your project, when you debug your example you may run into an exception Invalid Hook Call Warning. This issue explains the reason, your lib and example use a different instance, one solution is rewrite the react path in your example [app] package.json to 'file:../node_modules/react' or 'link:../node_modules/react'.

Note that in your case, you could simply change the path of react in the lib's devDependencies to point to the app's react:

  "devDependencies": {
    "@rollup/plugin-commonjs": "^20.0.0",
    "@rollup/plugin-node-resolve": "^13.0.4",
    "@rollup/plugin-typescript": "^8.2.5",
    "@types/react": "^17.0.18",
    "react": "file:../my-app/node_modules/react",

Or the other way around, depending on what makes the most sense.


There are also other ways to do local development of a React library:

  • Related