Reputation: 6762
I'm developing a React module locally. For that, I'm linking my module using npm link
.
The module is imported successfully but hooks are failing inside the module. It's throwing 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 See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
Checking the suggestions at React docs, I can confirm my app is using duplicate versions of React since the following code returns false:
// node_modules/mymodule/src/index.js
export { default as ReactFromModule } from 'react'
// src/index.js
import React from 'react'
import { ReactFromModule } from 'mymodule'
console.log(React === ReactFromModule) //false
This issue is full of suggestions but they are confusing. How can I solve it?
Note: Im not breaking rules of hooks, the error appears only when importing the module from an application.
Upvotes: 43
Views: 68798
Reputation: 11
What worked for me was:
webpack.config.js
: externals: {
react: "react",
"react-dom": "react-dom",
},
see https://github.com/code-forge-temple/circuit-sketcher-core/blob/main/webpack.config.js
package.json
: "peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
},
see https://github.com/code-forge-temple/circuit-sketcher-core/blob/main/package.json
webpack.config.js
: alias: {
react: path.resolve(__dirname, "node_modules/react"),
"react-dom": path.resolve(__dirname, "node_modules/react-dom"),
},
see https://github.com/code-forge-temple/circuit-sketcher-obsidian-plugin/blob/main/webpack.config.js
package.json
: "dependencies": {
"circuit-sketcher-core": "github:code-forge-temple/circuit-sketcher-core",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
see https://github.com/code-forge-temple/circuit-sketcher-obsidian-plugin/blob/main/package.json
vite.config.ts
with the following: resolve: {
dedupe: ["react", "react-dom"],
},
see https://github.com/code-forge-temple/circuit-sketcher-app/blob/main/vite.config.ts
package.json
: "dependencies": {
"circuit-sketcher-core": "github:code-forge-temple/circuit-sketcher-core",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
see https://github.com/code-forge-temple/circuit-sketcher-app/blob/main/package.json
Upvotes: 1
Reputation: 15662
Another approach that works if you're using Vite/Vitest is to use the resolve.dedupe
option.
From the docs:
If you have duplicated copies of the same dependency in your app (likely due to hoisting or linked packages in monorepos), use this option to force Vite to always resolve listed dependencies to the same copy (from project root).
Here's an example:
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react(), tsconfigPaths()],
resolve: {
dedupe: ["react", "react-dom"],
}
});
In my particular case the issue was caused by @testing-library/react
, so I've included that in my dedupe
list.
Upvotes: 5
Reputation: 6762
In the module you are developing, add the conflicting packages to peerDependencies
(and remove them from dependencies
or devDependencies
):
// package.json
"peerDependencies": {
"react": "16.13.1",
"react-dom": "16.13.1"
},
Execute npm install
in your module.
Now add react
and react-dom
to the webpack configuration of your module as externals
. These packages shouldnt be included in the bundle of the module (the app that uses the module will provide them):
// webpack.config.js
module.exports = {
/*
rest of config...
*/
output: {
filename: "index.js",
pathinfo: false,
libraryTarget: 'umd', // In my case, I use libraryTarget as 'umd'. Not sure if relevant
},
externals: {
// Use external version of React
"react": {
"commonjs": "react",
"commonjs2": "react",
"amd": "react",
"root": "React"
},
"react-dom": {
"commonjs": "react-dom",
"commonjs2": "react-dom",
"amd": "react-dom",
"root": "ReactDOM"
}
},
};
Then, after building your module, in your application you can check that both versions are now the same:
// node_modules/mymodule/src/index.js
export { default as ReactFromModule } from 'react'
// src/index.js
import React from 'react'
import { ReactFromModule } from 'mymodule'
console.log(React === ReactFromModule) // true :)
Upvotes: 39
Reputation: 23
In my case I was also missing import React from 'react'
from couple of files.
check this
Upvotes: -3
Reputation: 4371
I was attempting to use the peerDependencies
and removal of the devDependencies
and it was failing.
It turned out I had a node_modules
folder in one of the parent folders of the library I was working on and the duplicate version of React was being loaded from there instead of the tool that was trying to use the React library.
Rather than editing the devDependencies
to remove react I just wrote a small script to delete anything that's in the peerDependencies
from the node_modules
folder.
npm view --json=true . peerDependencies | jq -r 'keys | .[] | @text' | while read dep; do rm -r ./node_modules/${dep} && echo Removed ${dep}; done
Upvotes: -1
Reputation: 750
Adding react
and react-dom
as peerDependencies
in the package.json
didn't work for me.
I had to add an alias to the webpack configuration file:
// webpack.config.js
resolve: {
alias: {
react: path.resolve('./node_modules/react'),
}
Upvotes: 24
Reputation: 504
In response to another comment, merely moving React to peerDependencies does not adequately resolve the issue in all cases. I would reply to that comment directly, but StackOverflow requires more reputation to respond to wrong answers than it does to post them.
I have a shared React component module built using Webpack and have run into the same issue. I've outlined one possible fix in this comment below which requires modifying peerDependencies and using npm link in a fashion similar to the answer shared by mtkopone. https://github.com/facebook/react/issues/13991#issuecomment-841509933
My solution is a bit hacky and I wouldn't recommend it for long-term use. If you are using Webpack (which you tagged this question as), this article may detail a more permanent solution (https://medium.com/codex/duplicate-copy-of-react-errors-when-using-npm-link-e5011de0995d). I haven't tried it yet, but the author seems to have tried all the (incorrect) solutions out there and is also running into the hooks issue while trying to build shared component libraries.
The author of that article is trying to debug a Create-React-App app. While CRA uses webpack under the hood, you can't access the webpack.config directly, so the author has to perform some workarounds to do so. If you aren't using CRA, but just plain Webpack, then you could consider using the resolve.alias section of webpack.config to ensure there are no duplicate copies of React (see: https://blog.maximeheckel.com/posts/duplicate-dependencies-npm-link/)
Upvotes: 5