React: Hooks error from external component

Created on 7 Mar 2019  Ā·  8Comments  Ā·  Source: facebook/react

Do you want to request a feature or report a bug?
bug

What is the current behavior?
We are trying to create an external module which will be imported in main app.
Here is the module :
```import React, { useState } from 'react';
export const MyHelloComponent = (props) => {
const [test, setTest] = useState();
return (

Hello
);
}

Importing this simple code into main app gives us the error : 

> Uncaught Invariant Violation: Hooks can only be called inside the body of a function component

Removing hooks this code works fine.

We have followed instructions from [https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react](https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react) but error still thrown

If we link react as mentionned in documentation, works but it only can be done in development not in production.

We are not the only one having this issue [Side package with React Hooks failing with Invariant Violation when called in main package](https://stackoverflow.com/questions/54916349/side-package-with-react-hooks-failing-with-invariant-violation-when-called-in)

**If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:**

**Code from external component :** 
package.json

{
"name": "app-component",
"version": "1.0.0",
"main": "build/index.js",
"license": "MIT",
"private": true,
"peerDependencies": {
"react": "^16.8.4",
"react-dom": "^16.8.4"
},
"devDependencies": {
"@babel/core": "^7.3.4",
"@babel/plugin-proposal-class-properties": "^7.3.4",
"@babel/plugin-proposal-object-rest-spread": "^7.3.4",
"@babel/plugin-transform-react-jsx": "^7.3.0",
"@babel/plugin-transform-regenerator": "^7.3.4",
"@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.5",
"css-loader": "^2.1.0",
"react": "^16.8.4",
"react-dom": "^16.8.4",
"style-loader": "^0.23.1",
"styled-jsx": "^3.2.1",
"webpack": "^4.29.5",
"webpack-cli": "^3.2.3"
},
"scripts": {
"build": "./node_modules/.bin/webpack --mode production",
"dev": "./node_modules/.bin/webpack --mode development --watch"
},
"files": [
"build"
],
"dependencies": {
"@material-ui/core": "^3.9.2"
}
}
```
webpack.config.js

const path = require('path');
const pkg = require('./package.json');

const libraryName= pkg.name;

module.exports = (env, argv) => ({
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'index.js',
    library: libraryName,
    libraryTarget: 'commonjs2',
    publicPath: '/build/',
  },
  devtool: argv.mode !== 'production' ? 'inline-source-map': false,
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve(__dirname, 'src'),
        exclude: /(node_modules|bower_components)/,
        use: ['babel-loader']
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
    ]
  },

  resolve: {
    alias: {
      'react': path.resolve('./node_modules/react'),
      'react-dom': path.resolve('./node_modules/react-dom'),
    }
  },
  externals: {
    react: "react",
    "react-dom": "react-dom"
  }
});

src/index.js

export * from './components/hello';

src/components/hello.js

import React, { useState } from 'react';
export const MyHelloComponent = (props) => {
  const [test, setTest] = useState();
  return (<div>Hello</div>);
}

Code from main app:
webpack.config.js

module.exports = {
  entry: './src/index.js',
  output: {
    path: __dirname + '/dist',
    publicPath: '/dist/',
    filename: 'bundle.js'
  },
  devServer: {
    contentBase: './public',
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader']
      }
    ]
  },
};

package.json

{
  "name": "react-app-shell",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "webpack-dev-server --mode development"
  },
  "dependencies": {
    "react": "^16.8.4",
    "react-dom": "^16.8.4"
  },
  "devDependencies": {
    "@babel/core": "^7.3.4",
    "@babel/preset-env": "^7.3.4",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.5",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.2.3",
    "webpack-dev-server": "^3.2.1"
  }
}

src/index.js

import React from "react";
import ReactDOM from "react-dom";
import {MyHelloComponent} from 'app-component';

class Welcome extends React.Component {
  render() {
    return <><h1>Main App</h1><MyHelloComponent/></>;
  }
}
ReactDOM.render(<Welcome />, document.getElementById("root"));

To be able to make it works, you have to do yarn link after (or before) having built it from component and yarn link "app-component" from main app.

What is the expected behavior?
use hooks from external components works obviously.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
React 16.8.4 and with previous version it doest not work too

All 8 comments

Please provide a reproducing example as a GitHub project we can clone and try.

To be able to make it works, you have to do yarn link after (or before) having built it from component and yarn link "app-component" from main app.

Isn't this exactly what the link (https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react) suggests?

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. Assuming myapp and 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.

This should work (and you say it does).

If you're unhappy with this workaround, please investigate this with your bundler (webpack) or your package manager (e.g. Yarn supports Workspaces which solves this by hoisting). I'm not sure I see what is actionable for React in this issue.

To clarify again, you will only see this issue if you bundle two copies of React in your app — which is bad by itself. It's not a problem with Hooks, but a problem with your bundling process. Bundling duplicate React is bad for your users too because it increases the payload.

Thanks for answers,
In fact we are starting using React in more complex developments than examples in documentation, and may be we are wrong in some use of it.
So ok may be it's not an issue, but it's really strange to have differents behaviour of code when using classic code with class+stats and modern code with function+hooks

There is something incomprehensible for us ...

I've created 2 repo to let you see our code :
App Main
App Child

We're also looking about yarn workspaces and webpack to see if there is a solution using them...

If you have some advices on how to do with this kind of development, thanks in advance

Check out these resources: https://reactjs.org/community/support.html

We use issue tracker for bugs in React. But this is not a bug, it’s intended behavior. If you have duplicate React in the bundle you’ll have issues. You need to figure out why you have a duplicate React, and fix the root of the problem. Since that depends more on how you bundle the app than on React itself, I think asking elsewhere makes more sense.

the same error to me.

"peerDependencies": {
    "antd": "^3.25.0",
    "styled-components": "^4.4.1",
    "react": "^16.11.0",
    "react-dom": "^16.11.0"
  }
 externals: [
    // nodeExternals(),
    {
      react: {
        root: 'React',
        commonjs2: 'react',
        commonjs: 'react',
        amd: 'react'
      },
      'react-dom': {
        root: 'ReactDOM',
        commonjs2: 'react-dom',
        commonjs: 'react-dom',
        amd: 'react-dom'
      }
    }
}

but still #321 error怂

if i don't use useCallback ant useMemo in my npm comoponent,it can work,but i need useMemo...

If there any one still stucked with this problem,
Please check if you import react like this somewhere:

import React from 'React'

and

import React from 'react'

This causes bundler to import two React modules, as Dan pointed. ( https://github.com/facebook/react/issues/15050#issuecomment-470515270 )
I spent too much time because of this mistake. 😸

same problem using component from a custom library... hooks fail

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

I have done verification of React instace is the same

// 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);

Was this page helpful?
0 / 5 - 0 ratings