Behnam Azimi
Behnam Azimi

Reputation: 2488

Duplicate ReactJS import issue when using npm link to test component before publishing as npm package

I have a simple component like this.

import React, {useState} from 'react';

function MyComponentWithState(props) {
    const [value, setValue] = useState(0);

    return (
        <p>My value is: {value}</p>
    ) 
}

export default MyComponentWithState;

and I want to publish it on NPM as a separate package. so, to do that I prepared package.json and webpack.config.js like below.

package.json:

{
  "name": "try-to-publish",
  "version": "0.0.1",
  "description": "Just a test",
  "main": "build/index.js",
  "scripts": {
    "start": "webpack --watch",
    "build": "webpack"
  },
  "author": {
    "name": "Behnam Azimi"
  },
  "license": "ISC",
  "peerDependencies": {
    "react": "16.9.0",
    "react-dom": "16.9.0"
  },
  "dependencies": {
    "react": "16.9.0",
    "react-dom": "16.9.0",
    "prop-types": "15.7.2",
    "react-scripts": "3.1.1",
    "webpack": "4.39.3"
  },
  "devDependencies": {
    "@babel/core": "7.6.0",
    "@babel/plugin-proposal-class-properties": "7.5.5",
    "@babel/preset-env": "7.6.0",
    "@babel/preset-react": "7.0.0",
    "babel-loader": "8.0.6",
    "babel-plugin-transform-object-rest-spread": "6.26.0",
    "babel-plugin-transform-react-jsx": "6.24.1",
    "css-loader": "3.2.0",
    "node-sass": "4.12.0",
    "sass-loader": "8.0.0",
    "style-loader": "1.0.0",
    "webpack-cli": "3.3.8",
    "webpack-external-react": "^1.1.2"
  }
}

webpack.config.json:

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: 'index.js',
        libraryTarget: 'commonjs2'
    },
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                include: path.resolve(__dirname, 'src'),
                use: {
                    loader: "babel-loader"
                }
            },
        ]
    },
    resolve: {
        alias: {
            'react': path.resolve(__dirname, 'node_modules/react'),
            'react-dom': path.resolve(__dirname, 'node_modules/react-dom'),
        }
    },
    externals: {
        'react': "commonjs react",
        'react-dom': "commonjs react-dom"
    },
};

and here is my .babelrc:

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

These configs work like charm when I publish my component to NPM and install it in my another ReactJs project with `npm install , but my point is the local test!

I want to test this component/lib before publish. To do this I use npm link feature to link my component with my main ReactJS project.

As you saw above, my component is functional and I used hooks too. So when I inject the locally linked lib to my main ReactJs project face this 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

My issue is related to the 3td reason. My project uses ReactJs and import it once and also my component will import React! I mean twice React import in one project!.

I also have externals config about react and react-dom in my Webpack config.

What should I do to solve that? Where is my mistake?

Update: I also tried what @sung-m-kim and @eddie-cooro say but it not worked! Mean, I change the package.json and removed react and react-dom from dependencies and add them to devDpendencies.

Upvotes: 15

Views: 18309

Answers (5)

NiroshanJ
NiroshanJ

Reputation: 578

I also had the same issue. In my case, I developed some UI components as a package, and inside that I have an example folder with the React app which Create React App.

The issue is when I install the package into an example app using npm i ../ it installs all the files in the package to the example app including the node_modules folder. Since I have installed the react and react-dom as peer dependencies, the example app now has two different react copies.

Removing the node_module folder from the package and re-installing the package again fixed my issue.

Upvotes: 1

Eddie Cooro
Eddie Cooro

Reputation: 1968

Set the react and react-native packages only inside of the peerDependencies part of package.json, not the dependencies. Also for local development (When your package is not included in any other react projects and you want to to run it locally), you can use the devDependencies field.

Upvotes: 3

armin yahya
armin yahya

Reputation: 1496

I finally solved this problem by these steps.

run npm link inside

<your-library-package>/node_modules/react

also

run npm link inside

<your-library-package>/node_modules/react-dom

then run npm link react and npm link react-dom inside your application root directory

and dont forget to keep react and react-dom as externals in the library

// webpack.config.js

const externals = {
    "react": "react",
    "react-dom": "react-dom",
}

module.exports = {
    .
    .
    .

   externals
   }

Upvotes: 11

Marbin274
Marbin274

Reputation: 509

I resolve this problem in a typescript react project.

probably, when use the npm link use the react from main app project and the component project.

So, in your package.json remove react from dependencies and/or devDependencies

Check the answer: https://stackoverflow.com/a/62807950/5183591

Upvotes: 1

Behnam Azimi
Behnam Azimi

Reputation: 2488

I solved my issue. I used RollupJS instead of Webpack for bundling as bundle tool.

Here is my rollup.config.js:

import {uglify} from 'rollup-plugin-uglify'
import babel from 'rollup-plugin-babel'

export default {
    input: "./src/index.js",
    external: ['react', 'react-dom'],
    output: {
        name: 'test-lib',
        format: "cjs",
    },
    plugins: [
        babel({
            exclude: "node_modules/**"
        }),
        uglify(),
    ],
};

and my package.json:

{
  "name": "test-lib",
  "version": "1.0.0",
  "main": "dist/test-lib.min.js",
  "scripts": {
    "build": "rollup -c -o dist/test-lib.min.js"
  },
  "author": "Behnam Azimi",
  "license": "ISC",
  "peerDependencies": {
    "react": "^16.9.0",
    "react-dom": "^16.9.0"
  },
  "devDependencies": {
    "@babel/core": "^7.6.0",
    "@babel/preset-env": "^7.6.0",
    "@babel/preset-react": "^7.0.0",
    "rollup": "^1.21.4",
    "rollup-plugin-babel": "^4.3.3",
    "rollup-plugin-commonjs": "^10.1.0",
    "rollup-plugin-uglify": "^6.0.3"
  }
}

After these changes, npm link worked truly in my ReactJS (Hooks) project.

Notice that it's just a simple Rollup config to show my solution and you can add many kinds of stuff like hot reloading, styles loaders, and many other plugins to the config.

Upvotes: 2

Related Questions