facebook/react

Hooks + multiple instances of React

brunolemos opened this issue ยท 499 comments

To people coming from search: please read this page first. It contains most common possible fixes!

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

Enhancement

What is the current behavior?

I had multiple instances of React by mistake.

When trying to use hooks, got this error:
hooks can only be called inside the body of a function component

Which is not correct since I was using function components. Took me a while to find the real cause of the issue.

What is the expected behavior?

Show the correct error message. Maybe detect that the app has multiple instances of React and say that it may be the reason of bugs.

So just for clarification: You were importing a hook (say useState) from a different react module than the one used to render the component?

I agree that this is confusing. I'm not sure though if we have a way of knowing if any other React module is rendering. AFAIK we try to run React in isolation as much as possible so that multiple React instances can work in the same global context without issues.

Otherwise we could probably update the error message and mention this case as well if it's not too confusing.

Yes, I compared React1 === React2 and it was false (React1 being from index.js and React2 being from the file using the hook). When this happens, hooks fail with the generic error message above.

This issue is to raise awareness of this case and maybe improve the error message in some way to help people that face this. It's probably very edge though.

Yup, i tried to npm link a package i'm creating. It throws that same error since the other package is also using hooks but with its own React. I had to publish my package to NPM and then import it directly from NPM. That way the error was gone, but i hope this is fixed since publishing a package without testing it is bad, obviously

Lerna monorepos suffer from this as well when a custom hook is defined in one package and used by another as the symlinked dependencies use their own copy of react.

I have 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"

I had the same issue and I resolved it by adding:

 alias: {
        react: path.resolve('./node_modules/react')
      }

to resolve property in webpack config of my main app.

It's was obviously my mistake of using two copies of React but I agree that it would be great if the error message was better. I think this is maybe similar to: #2402

@mpeyper It works. Thanks

jimbo commented

@apieceofbart That worked for me. Thanks for the suggestion. ๐Ÿ‘

As I understand this problem arises when there are multiple copies of React in the same bundle.

Is this also a problem if two separate bundles with their own copies of React are bootstrapping their own React applications on separate dom elements, like described here: https://medium.jonasbandi.net/hosting-multiple-react-applications-on-the-same-document-c887df1a1fcd

I think the latter is a common "integration" pattern used for instance in the single-spa meta-framework (https://github.com/CanopyTax/single-spa).

I'm also having this issue even with the exact same react versions, developing hooks to be published on their own is broken when using npm-link. Getting the same unhelpful hooks can only be called inside the body of a function component message. @apieceofbart's alias solution solved this for me. Thanks so much!

Same issue here when I npm link a package to my main application. I could not get babel-plugin-module-resolver working.
It says:
Could not find module './node_module/react'
This is annoying because it prevents me from testing my component locally before publishing it.

I fixed my issue by removing the caret in "react": "^16.7.0-alpha.2"
Here is the full comment: #14454 (comment)

I'm using Yarn, and fixed this by forcing resolution in my package.json:

  "resolutions": {
    "**/react": "16.7.0-alpha.2",
    "**/react-dom": "16.7.0-alpha.2"
  },

Same here!!

Just wanted to leave a note here for anyone who might have had this problem in the same manner I did.

We're running React and Rails with the react-rails gem and rendering components directly into Rails views. I was receiving this error every time a new version of the app was pushed, because Turbolinks was grabbing the new JS bundle out of the <head> which loaded up an extra instance of React. Solution was to have Turbolinks do a full page reload when it detects the bundle has changed: https://github.com/turbolinks/turbolinks#reloading-when-assets-change

This appears to have solved it for us.

I'm very excited to finally put Hooks into production, and we all owe a huge thank you to everyone who made it possible. They're a ton of fun to work with and have made my code shorter and more declarative.

Just as a heads up, this issue is still relevant in the released version with the same unhelpful error message of "Hooks can only be called inside the body of a function component."

Is this something that can be fixed? I imagine it might become more and more prevalent as more devs start to implement the new features, and a clearer error message would go a long way in lieu of an outright "fix".

Thanks again for all the hard work and congrats on the release! It's really an amazing set of features.

Edit: Should have looked closer at the open PRs, just found #14690 that addresses this. Thanks @threepointone!

@taylorham The link in the commit doesn't point to anything yet. I'll wait for it, but this is an issue I have been having since using hooks in a linked (as of npm link) package and it's impossible to work with it locally without publishing.
After looking severals issues, I tought this was an issue with react-hot-loader that was compiling components to classes, but even after they released a version with Hook support, it still fails the same way.
I've tried a lot of different hacks but no luck. I don't know why everybody hasn't been struck with this issue yet ๐Ÿง

@dotlouis Yeah, it's just an updated error message so far and the issue itself is still a pain.

The only thing that has worked for me at all is to make whatever app I'm developing depend on the library's instance of React by using "react": "link:../my-library/node_modules/react".

  • none of the proposed resolutions did work for me, and i've been trying all
  • trying to install on a project implementing context and a lot of HOCs
  • starting from a blank project did the trick
  • i'm keeping on looking for the cause

[ok] for me, correction was not about package.json or others double react cause : i had a global themeProvider on top of my app, coming from context. Replacing it with a "useContext Hook" ( while rewriting it as a functional comp ) seemed to be the only solution
Maybe is there an issue when

<GoodOldContext iam={a class component}>
    <BrandNewHook>
             errors : Hooks can only be called inside the body of a function component #35
     </BrandnewHook>
</GooOldContext>
export withGoodOldContext.consumer(here component)

I'm developing a component where there is an example folder that uses create-react-app.

Doing this in package.json resolved this issue for me:

{
    ...
    "dependencies": {
        "my-component": "link:..",
        "react": "link:../node_modules/react",
        "react-dom": "link:../node_modules/react-dom",
        "react-scripts": "2.1.3"
    },
    ...
}

@taylorham @DylanVann Thanks for your input guys. Unfortunately, it still does not work for me.
And I could not find any documentation about this link: protocol you used.
Basically, it says that "react-spring" (another dep that also uses react as a dependency) cannot find react-dom. Can you point me to some documentation about "react": "link:../some/path" please?

I am using linked UI package as well and I was able to fix this issue.
You need to export react renderToString from UI (linked package).
I created render function in the linked package.

Just for a better context - #14257

Thanks @theKashey. @gaearon seems to think that it is the normal behavior. I get that React should not be loaded twice, but what is the recommended way of working with a linked local package then?

I also had issues with Lerna workspaces getting symlinked properly. This was the trick I used to get this to work. Be sure to run npm install afterwards.

"dependencies": {
	"react-dom": "file:../common/node_modules/react-dom",
	"react": "file:../common/node_modules/react"
}

There is many ways to solve it, and yarn resolutions would not usually help - it's more related to the "building tool"

  • for webpack use aliases - just "hard" alias everything ducks like react to a single file
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
    alias: {
     react: path.resolve(path.join(__dirname, './node_modules/react')),
   }
import {setAliases} from 'require-control';
setAliases({
  'react': path.resolve(path.join(__dirname, './node_modules/react'))
});
  • for jest use moduleNameMapper
"jest": {
    "moduleNameMapper": {
      "^react$": "<rootDir>/node_modules/$1",
    },

@theKashey thanks for your insights, this makes sense when we consider how module resolution is done (bottom, then up the tree), but from a user point of view I don't find this very practical. When I npm link a package, I would expect it to work without having to re-wire dependencies explicitly. This makes developing a package locally quite painful.

This is a cornerstone, this is how node_modules designed to work, this is why you might have two versions of a Button in two different major versions, and dependent modules will easely find the "right" version of a package.
This is how it should work all the time.

Node.js internals are quite straightforward - try to open a file adding all known prefixes (like node_modules) or extensions(like js, ts, json); if none found go one directory up. The only way to "fix it" - replace nodejs module resolution system.

yarn pnp will do that, and might solve the problem. yarn workspaces, which might hoist shared packages to the top - will also solve the problem without any "magic" involved.

npm workspaces? Does not exists right now.

I actually ended up switching my project to use workspaces. It resolves this without having to use resolutions, and the hoisting/structure is beneficial anyways.

This one was a headscratcher. I tried the webpack resolve.alias setting but it was not working, tried many settings too but never really managed to get it to work unfortunately, but here's how I finally managed to get it to work:

Here's my folder structure:

Project
|
+-- node_modules
|
+-- build
| |
| +-- index.js
|
+-- example (create-react-app)
| |
| +-- package.json

I had to modify my package.json inside the example folder, essentially pulling react from the project's 'main' node_modules based on @jmlivingston's suggestion, here's how it end up:

  "dependencies": {
    "react": "file:../node_modules/react",
    "react-dom": "file:../node_modules/react-dom",
    "react-scripts": "2.1.5"
  },

Now after that I ran npm install and then I ran npm link, that did the trick.

Hopefully this can help someone else and save some time.

So any fix to this issue? I've tried as many recommendations here as I can and no luck. I am using create-react-app and typescript. Using React/React-dom 16.8.3. This is a new project I created 2 days ago so pretty plain. I am using useSpring() and animated.div. Thanks

@guru-florida are you using react-router by any chance?

I'm using the same stack as you (typescript & create-react-app) and my issue with with the render attribute. Changing it to component did the trick.

Before:

<Route path="/signup" render={SignUp} />

After:

<Route path="/signup" component={SignUp} />

Hope it helps..!

@Mikeyyyyyy No, not using React Router in this one. Thanks for the tip though cuz I was in the last project I tried using spring and had the same issue.

tj commented

I had this issue with npm link (in a parcel app), the npm link .../whatever/node_modules/react doesn't seem to resolve it, works fine with non-hook components though

@tj I guess you have problem with ssr. Fast workaround is to export react functions or whole react from linked package and import it in your server package

tj commented

@seeden ahh I'm not using SSR, just a SPA w/ Parcel. I have a ui pkg internally for my own stuff and an app I'm working on, both have the same react version, seems odd that there's a duplicate but maybe that's a Parcel concern

@tj oh, I see. Then good luck with this very strange issue. I spent one week with this

So any fix to this issue?

There is no issue here per se. As explained on this page, React needs useState() calls to be on the same react object as the react object as "seen" from inside react-dom. If that's not what happens for you, it means you're bundling two copies of React on the page โ€” which is bad by itself and also breaks some other features before Hooks. So you'll want to fix it anyway. This page contains common ways to diagnose to fix it.

We're leaving this discussion open for sharing particular workarounds when people run into this problem. But it's not an issue per se that can be "fixed" by anyone but you.

I had this issue with npm link (in a parcel app), the npm link .../whatever/node_modules/react doesn't seem to resolve it, works fine with non-hook components though

Do you mind creating a small repro case?

tj commented

@gaearon will do, should have time to dig in a bit more next week

Happily, require-control has fixed our issue with yarn link + SSR + styled-components 4's static context. Thanks @theKashey ๐Ÿ‘

I tried everything here and failed. It was actually something different not documented here. It was to do with the case sensitivity of the react imports. In some cases we had:

import React from 'react'

And in others:

import React from 'React'

On some file systems (unix, osx) this causes Webpack to instantiate two copies of React.

This caused extreme confusion as I could clearly see we only have one copy of React; but it was instead the way we were importing it.

The test on the react documentation also comes out fine as it obviously uses only lower case.

This sounds like it could be worthy of a mention in the docs?

For me the reason of multiple instances of React was Webpack DllPlugin. For my vendor DLL I didn't include react and react-dom to my the entries list, however, I had other libraries which required react or react-dom so my DLL contained react and react-dom (quick check of the manifest json file can reveal that). So, when I was running the code, and import React into the application it was loading it from node_modules, but in the vendors' code React was required from their DLL file.

Overall: be careful with DLL files and make sure your included modules don't include extra dependencies that you don't need otherwise you will double import them.

I was able to fix this by updating react-hot-loader to 4.6.0

tj commented

this did the trick for the npm link stuff in Parcel:

"alias": {
		"react": "../ui/node_modules/react",
		"react-dom": "../ui/node_modules/react-dom"
	}

not sure if that's what it'll try to use for a production build, seems kind of hacky but it works for development at least

@theKashey OMG man, it works! I've tried many different solutions that people suggests related to this issues: mangling with package.json deps, tracing "two reacts" across project, checking if I'm breaking the *rule of hooks` (which I'm not), but I think that your option with:

alias: {
      react: path.resolve(path.join(__dirname, './node_modules/react')),
      'react-dom': path.resolve(path.join(__dirname, './node_modules/react-dom'))
    }

allows us to move our project to the next lvl, using hooks in our app-as-a-lib.

This is the resulted webpack.config.js

npm ls react

returns

web@0.0.0 D:\code\project
`-- (empty)

for me

console.log(window.React1 === window.React2) returns true
at this point i'm thinking it's SSR causing the issue

Update. It was indeed caused By React-apollo's SSR behaviour (apollographql/react-apollo#2541)
Upgrading to 2.3.1 fixed it

Hi guys, our team face this problem and took few days to sort it out.

the working solutions for us

Solution A: specify the package position to look for, as mentioned above

  alias: {
      react: path.resolve(path.join(__dirname, './node_modules/react')),
      'react-dom': path.resolve(path.join(__dirname, './node_modules/react-dom'))
    }

Solution B: use webpack resolve.modules to prioritise the right node_modules folder to look for modules

case background and why it happens

First thing first, it's not react's fault, it's not even lerna's, but react, webpack, and npm-link might need to take some responsibilities.

Case requirement:

-Non-monorepo:

  • have symlinked packages
  • symlinked package has exported component using hooks
  • creating react client side pages
  • If working on a monorepo
    • packages symlinked
    • packages got different versions of dependencies (even patch version difference), so even workspace will solve as 2 react installed
    • entrance package imported a symlinked package which use hooks

Example

Structure

- mono repo root
  - packages
    - ComponentWithHooks (peerDependency: react@^16.8.1)
    - ProductA (dependency: ComponentWithHooks, dependency: react@^16.8.4)
    - ProductB (dependency: react@^16.8.1)

Once bootstrap with workspaces, it will resolve to

- mono repo root
  - node_modules
    - react(16.8.1)
  - packages
    - ComponentWithHooks
      - node_modules (empty)
    - ProductA
      - node_modules
        - react(16.8.4)
    - ProductB
      - node_modules (empty)

And once you serve ProductA with webpack or maybe something else, it will contain 2 react instances.

Code in ProductA, will looks for ProductA/node_modules/react.

But the imported ComponentWithHooks will look for mono repo root/node_modules/react.

Why? Remember the look up rules of npm? If it cannot find the module in it's own node_modules folder, it will look for parent's node_modules...

So tools like webpack applied this rule in default perfectly.
It's nothing wrong util mono repo solution become popular.
And normal package won't notice this as most of them do not require single instance as react and redux.

I'm having this same issue using a very basic reproduction using yarn workspaces example - https://github.com/mwarger/yarn-workspace-hooks-repro

I have a component-library that is written in typescript and bundled with parcel. The example-demo is what will showcase this component-library and is a freshly created CRA app. All common packages are hoisted with yarn, so in theory there should only be one version of react available. However, the React.useEffect call I'm making in index.tsx causes the error that leads me to this GitHub issue.

Everything works until a hook is added. To reproduce the error, uncomment lines 7-9 in component-library/src/index.tsx

Hopefully I'm doing something silly that I have overlooked. Please advise as to any steps I may use to try and remedy this. Thank you!

Follow-up Edit: The below suggested debug script output prints true for me. It appears that I do not have two Reacts.

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

Took me several hours so it might worth to take a note here.

In my case, I put a line of <script defer src="./dist/bundle.js" /> in the head of the HTML template, which works as normal when not using React hooks. All the solutions don't work and the window.React1 == window.React2 check returns true in this case.

Since webpack will inject the script tag afterward, the template should not have script tag by its own. Remove the script tag from the template make React functional with hooks (pun intended) again.

In my case I have a React app which was npm linked to a dependency I was working on. This will do the trick until I can fix a couple dependencies that need to move react and react-dom to dev and peer deps.

  1. From the app: cd node_modules/react && npm link
  2. From the app: cd node_modules/react-dom && npm link react
  3. From the linked package: npm link react

Why does it work? The error warning page mentions that "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".

I am still having this issue, despite trying all of the above. Standard webpack4/babel config, with preset-env and preset-react plugins. My react/react-dom versions are pinned to 16.8.4 using yarn resolutions (where also the window.React1 === window.React2 check from above returns true).

This is on the most basic of usages:

import React, { useState } from "react";

function MyComp() {
  const [hello] = useState(0);

  return <div>HELLO {hello}</div>;
}
export default MyComp;

Does anyone have any other ideas?

EDIT: To clarify, the error is shown as react.development.js:88 Uncaught Invariant Violation: Hooks can only be called inside the body of a function component. as per the OP

In my case I have a React app which was npm linked to a dependency I was working on. This will do the trick until I can fix a couple dependencies that need to move react and react-dom to dev and peer deps.

  1. From the app: cd node_modules/react && npm link
  2. From the app: cd node_modules/react-dom && npm link react
  3. From the linked package: npm link react

Why does it work? The error warning page mentions that "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".

Thanks! This works great for me. (even when I use npm link and symlink mixed situation)

I have tried all of the suggested above and was still having the error.

With a little help from @inverherive we found that enzyme-adapter-react-16 was still causing issues.

Whilst we updated react-test-renderer to the latest version (16.8.4) as it only recently added hooks support, we found via npm ls react-test-renderer that the latest version of enzyme-adapter-react-16 (1.11.2) had an internal dependancy of react-test-renderer@16.4.2, which doesn't support hooks.

โ”œโ”€โ”ฌ enzyme-adapter-react-16@1.11.2
โ”‚ โ””โ”€โ”€ react-test-renderer@16.4.2 
โ””โ”€โ”€ react-test-renderer@16.8.4

To fix this issue , as well as following @chulanovskyi's fixes, as we are using yarn we added react-test-renderer resolutions to our package.json . This forces all references of react-test-renderer to use "16.8.4".

  "resolutions": {
    "react-test-renderer": "16.8.4"
  },

This was mega frustrating, hope this can help someone else. Thanks to @chulanovskyi and @theKashey for their suggestions as well.

This will do the trick until I can fix a couple dependencies that need to move react and react-dom to dev and peer deps.

@ajcrews (I might have missed something but) I npm link an internal library in and that library has react in peerDependencies and devDependencies and I still needed your fix regardless to solve the error. Nice find!

I was about to post but found a solution

I have a component library, with an example CRA app inside for development

In the CRA app's package.json I had to modify react and react-dom to "borrow" from the root component's package.json

"dependencies": {
  "react": "link:../node_modules/react",
  "react-dom": "link:../node_modules/reac-dom",
}

This was mega frustrating, hope this can help someone else. Thanks to @chulanovskyi and @theKashey for their suggestions as well.

@Paddy-Hamilton Always check your lockfile after an install. I had encountered the same issue where yarn was duplicating react-test-renderer. With a little bit of surgery in your lockfile you could fix those:

yarn add -D react-test-renderer

-react-test-renderer@^16.0.0-0, react-test-renderer@^16.1.1:
+react-test-renderer@^16.0.0-0:
  version "16.8.4"
  resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.4.tgz#abee4c2c3bf967a8892a7b37f77370c5570d5329"
  integrity sha512-jQ9Tf/ilIGSr55Cz23AZ/7H3ABEdo9oy2zF9nDHZyhLHDSLKuoILxw2ifpBfuuwQvj4LCoqdru9iZf7gwFH28A==
  dependencies:
    object-assign "^4.1.1"
    prop-types "^15.6.2"
    react-is "^16.8.4"
    scheduler "^0.13.4"

+react-test-renderer@^16.8.5:
+  version "16.8.5"
+  resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.5.tgz#4cba7a8aad73f7e8a0bc4379a0fe21632886a563"
+  integrity sha512-/pFpHYQH4f35OqOae/DgOCXJDxBqD3K3akVfDhLgR0qYHoHjnICI/XS9QDwIhbrOFHWL7okVW9kKMaHuKvt2ng==
+  dependencies:
+    object-assign "^4.1.1"
+    prop-types "^15.6.2"
+    react-is "^16.8.5"
+    scheduler "^0.13.5"

A yarn check would already warn you

$ yarn check
warning "enzyme-adapter-react-16#react-test-renderer@^16.0.0-0" could be deduped from "16.8.5" to "react-test-renderer@16.8.5"

then manually dedupe it by applying the following patch:

-react-test-renderer@^16.0.0-0:
-  version "16.8.4"
-  resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.4.tgz#abee4c2c3bf967a8892a7b37f77370c5570d5329"
-  integrity sha512-jQ9Tf/ilIGSr55Cz23AZ/7H3ABEdo9oy2zF9nDHZyhLHDSLKuoILxw2ifpBfuuwQvj4LCoqdru9iZf7gwFH28A==
-  dependencies:
-    object-assign "^4.1.1"
-    prop-types "^15.6.2"
-    react-is "^16.8.4"
-    scheduler "^0.13.4"
-
-react-test-renderer@^16.8.5:
+react-test-renderer@^16.0.0-0, react-test-renderer@^16.8.5:

Now you have a single version of react-test-renderer without any resolutions or webpack alias shenanigans.

For any issues related to linked packages and create-react-app follow facebook/create-react-app#6207

There is many ways to solve it, and yarn resolutions would not usually help - it's more related to the "building tool"

  • for webpack use aliases - just "hard" alias everything ducks like react to a single file
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
    alias: {
     react: path.resolve(path.join(__dirname, './node_modules/react')),
   }
import {setAliases} from 'require-control';
setAliases({
  'react': path.resolve(path.join(__dirname, './node_modules/react'))
});
  • for jest use moduleNameMapper
"jest": {
    "moduleNameMapper": {
      "^react$": "<rootDir>/node_modules/$1",
    },

This did it for me.

@ajcrews
Thank you! Works brilliantly to me!

I made a little test case with minimal setup using react 16.8.6, electron-webpack and RHL. Notably, when this error occurs the entire browser (in this setup, electron) just starts to use up a whole bunch of CPU time)

https://github.com/PerfectionCSGO/reeee

I've been bashing my head over this problem for 3 days now. Originally I thought RHL was the problem but removing that entirely from this project won't resolve the issue.

npm ls react returns only one result. I ensured that the above fix is applied with the latest versions + webpack alias.

The code will work in a sandbox.

In a simple webpack/website the code will work without problem. However, with electron-webpack this problem persists.

  "dependencies": {
    "i18next": "^15.0.9",
    "i18next-browser-languagedetector": "^3.0.1",
    "react": "^16.8.6",
    "react-dom": "npm:@hot-loader/react-dom",
    "react-hot-loader": "^4.8.2",
    "react-i18next": "^10.6.1",
    "source-map-support": "^0.5.11",
    "tslint": "^5.15.0"
  },
  "devDependencies": {
    "@babel/core": "^7.4.3",
    "@babel/preset-react": "^7.0.0",
    "@babel/preset-typescript": "^7.3.3",
    "@types/react": "^16.8.12",
    "@types/react-dom": "^16.8.3",
    "electron": "^4.1.3",
    "electron-builder": "20.39.0",
    "electron-webpack": "^2.6.2",
    "electron-webpack-ts": "^3.1.1",
    "typescript": "^3.4.1",
    "webpack": "^4.29.6"
  }

I hope someone can give me a pointer...

When I replace react-l18next with mobx-react-lite and use observer, it will cause the same effect.

In regards to my issue, I have resolved it by elbowing electron-webpack and go with a more 'pure' electron solution. My guess is that it's a toolchain used in webpack or babel that's incompatible.

I encountered this issue only in production. Non of the proposed solutions here helped.
My use case was a third party app that loads as a widget in another website.
When the site first loaded with the widget all worked well, but when the user navigate to a different page and returned to the page with the widget, I got the hooks error.

Note that the error only occurs when the navigation doesn't cause a page reload.

I spent hours trying to figure out what was the issue. Finally the problem was with the code snippet that loads the app bundle. On page change the bundle could load multiple times, which caused I guess to a multiple React instances in the same name space.

I fixed it by checking if the script has already been loaded.
First I exported my library to the global namespace by using Webpack's 'library' configuration:

output: {
    library: 'myLib',
    ...
}

And then in the loading script I checked if the library exists or not:

if(!window.myLib){
    var bz = document.createElement('script');
    bz.type = 'text/javascript'; 
    bz.async = true;
    bz.src = 'https://path/to/bundle.js';
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(bz, s);
}

It might be a very specific use case but I hope this can help someone else.

So we have a main React application leveraging webpack.
We're trying to create a small React library that uses hooks, we've tried swapping out bundlers (parcel, pure babel, webpack).
When trying out our webpack implementation, we marked react and react-dom as external, so we won't include them in our bundle.
We still get the same hooks exception when using npm link.

Creating a symlink to the main application's react does work, but it's not a great dev workflow.

I'm having a rough time figuring out the underlying cause of the issue. What's producing duplicate React instance?

Hi @adriene-orange , you might find my post #13991 (comment) for more explanations.

Multi-instance caused by npm link is because node by default will look up in parent folder's node_modules for the module if it cannot find in your package.

The simplest and best solution we find for this is in your entrance package's webpack (or other tools) configuration, there's something like resolve.modules to manually set the paths and order for the paths that webpack will look for modules. Exp., resolve: { modules: [path.resolve(PACKAGE_ROOT, 'node_modules'), 'node_modules'] }, will force webpack to find modules in your entrance package root's node_module first. If cannot find the module in root, then find in the relative node_modules folder...

So we have a main React application leveraging webpack.
We're trying to create a small React library that uses hooks, we've tried swapping out bundlers (parcel, pure babel, webpack).
When trying out our webpack implementation, we marked react and react-dom as external, so we won't include them in our bundle.
We still get the same hooks exception when using npm link.

Creating a symlink to the main application's react does work, but it's not a great dev workflow.

I'm having a rough time figuring out the underlying cause of the issue. What's producing duplicate React instance?

Hi im getting this error

Invariant Violation: 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://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.

   5 | 
   6 | const useApiHelper = (url, reducer) => {
>  7 |     const [state, dispatch] = useReducer(reducer, {});
     |                                                  ^
   8 | 
   9 |     useEffect(() => {
  10 |         fetch(url).then(res => res.json())

Sample code https://stackblitz.com/edit/react-mbze9q

When i try to access this function inside test cases i'm getting above error

@abhishekguru You are calling the Hook outside of a component here in your test:

test('API test', async () => {
  const newState = useAPIHelper( // <- Called outside of a component
    'https://jsonplaceholder.typicode.com/posts',
    reducer
  )
  console.log(newState, 'new');
  // expect(newState[samUrl].loading).toEqual(true);
});

As the error states, hooks can only be called from another hook, or from within a component. In your case you could create a component for your test and render that component which uses the hook if you'd like.

Shameless plug

@abhishekguru if you have a generic hook used between multiple components and you want to test it independently of any particular component, you might consider using react-hooks-testing-library.

import { renderHook } from 'react-hooks-testing-library'

test('API test', async () => {
  const { result } = renderHook(() => useAPIHelper( // <- now called within a component
    'https://jsonplaceholder.typicode.com/posts',
    reducer
  ))

  console.log(result.current, 'new');
  // expect(result.current[samUrl].loading).toEqual(true);
});

I wanted to chime in here because we had issues with SSR only. We clear node's require.cache on file changes. This effectively gives hot reloading on the server. Clearing node's require.cache will cause issues with libraries that need to have a single copy. Here's our solution:

Object.keys(require.cache)
  .filter(key => !isSingleton(key)) // This is needed here because react cannot be deleted without causing errors
  .forEach(key => {
    delete require.cache[key]
  })

Our isSingleton function contains the list of libraries that must have a single copy. A good rule of thumb is any library that needs to be defined in peerDependencies

https://yarnpkg.com/lang/en/docs/dependency-types/#toc-peerdependencies

Also had the same issue and for me

window.React1 = require('react');
require('react-dom');
window.React2 = require('react');
console.log(window.React1 === window.React2); // true

also returned true, tried all given suggestion but nothing worked. Finally it turned out that:

Webpack adds script tag with the bundle.js into the index.html automatically. My issue was because I was adding the bundle.js into index.html explicitly, which used to work fine before hooks.

For me issue was after babel 7 upgrade, there was no separate versions with npm ls react. Removing
"react-hot-loader/babel" from .babelrc fixed the issue temporarily.

I tried all the solutions above, but still got the error.
Eventually, I found it was caused by the package why-did-you-update, and there is a related issue for it. Just a clue for anyone uses a similar package which modifies React.

I was able to fix this in a react-native + Yarn Workspaces scenario.


In root package.json

{
  "private": true,
  "workspaces": {
    "packages": [
      "packages/*"
    ],
    "nohoist": [
      "**/react-native",
      "**/react-native/!(react)/**"
    ]
  }
}

This prevents react-native code from being hoisted (as is needed for react native), while still hoisting react, thus having all shared modules use the same react.


In the metro.config.js

module.exports = {
  watchFolders: [
    path.resolve(__dirname, '../', 'shared-module'), 
    // ...more modules
    path.resolve(__dirname, '../', '../') // to make sure the root `react` is also part of the haste module map
  ]
}

The metro config lets the bundler know where everything is.

I found a way to solve the problem for people who are locally developing a npm package for example, and they are trying to test it locally by loading their package using npm link in some sort of example app.

I switched from example app (original way of testing the component) to Storybook. When using the storybook in the project it will not load React twice as it will use the same one that component is using. While using npm link I had problems with hooks and React being loaded twice, also I was not able to get any of the above solutions to work. So Storybook solved my problem, and now I have a way to test my component through multiple scenarios and at the same time build some interactive documentation for it.

Sharing my experience with this, solved it for our project by using webpack externals for the component library to exclude react and react-dom from the build.

We are just starting with React. In my case, we are starting with Lerna monorepo with Neutrino components library package and Neutrino webapp client package. The webapp consume the build product of the linked components library. After some experimenting and getting true for the same React instance, I have looked to a way to exclude react and react-dom from the build of the components library at all.

It seems a common design pattern solution for webpack components libraries so in the components library I have added to the webpack config:

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

I didn't need to put a global script tag in the webapp package for react and react-dom. I am guessing Webpack is providing those to the component library in its require implementation the same as it provides to the webapp.

another cause of this error is mis-configuring React Router's Route,

This fails:

<Route render={MyHookedComponent}/>

, but this succeeds:

<Route component={MyHookedComponent}/>

Eg. you need to use component not render. This is an easy mistake to make since render generally works fine with class based components.

I was working on biolerplate and want to publish it to npm, and developing with the help of npm link It was working properly but after sometime started giving errors Invalid Hook call warning.
I tried using npm link ../myapp/node_modules/react but it doesn't solve out my problem,
And compared React1 === React2 it is true as well as npm ls react also done, it shows only one package.

And i am not using webpack also, i am just adding some layer outside create-react-app so i can't forced my app to use local installed react module.
Stucked with it from past 3 days._

I experienced this warning during server side rendering (SSR) because I was using an older version of react-apollo so just wanted to leave this link here to help out the next poor soul who runs into this issue:

apollographql/react-apollo#2541

In short, getDataFromTree doesn't support react hooks until version react-apollo@2.3.1.

I had the same issue and I resolved it by adding:

 alias: {
        react: path.resolve('./node_modules/react')
      }

to resolve property in webpack config of my main app.

It's was obviously my mistake of using two copies of React but I agree that it would be great if the error message was better. I think this is maybe similar to: #2402

Any suggestions for doing this with create-react-app?

^ Ok, the solution I went for to solve this for create-react-app is to use react-app-rewired and customize-cra.

Here is my config-overrides.js :

const {
    override,
    addWebpackAlias,
  } = require("customize-cra");

const path = require('path'); 

module.exports = override( 
    addWebpackAlias({
        react: path.resolve('./node_modules/react')
    })
)

Example project: https://github.com/dwjohnston/material-ui-hooks-issue/tree/master

In our team we have a universal navigation component work across tens of apps, all these apps comes from react 15.0.0 to react 16.8.0, in order to enable navigation implemented above hooks, we have to bundle it with a latest react

In this case, having multiple instances of react is a fundamental requirement for us, I'd like to know whether react official team is willing to solve this issue in the future?

@dwjohnston my workaround for create-react-app was to create a webpack config for development. create-react-app internally uses webpack, webpack-dev-server, and the babel-loader so creating a webpack config just for development wasn't too bad because the dependencies are already implictly there but still a good amount of overhead to get the working correctly.

I have a issue on create-react-app: facebook/create-react-app#6953 to add webpack alias support or similar.

๐Ÿ‘‹ If anyone is also using create-react-app and experiencing this pain point, could you please give that issue a thumb ups? It might help prioritize the issue.

@ricokahler - Thanks for point that out. I'm glad to see that I'm not the only person with this issue - I've run into it with context as well.

Is there any resources that you know of that discuss this issue further?

If you're in my boat, you've added a react component package from a local directory, and now it automatically builds and install it, along with its own copy of node_modules (because it uses npm link to do this), giving your app 2 copies or React now.

I've worked around it by deleting the node_modules/<my_package>/node_modules before running the app. To do this automatically:

"prestart": "rimraf ./node_modules/<my_package>/node_modules"

I also faced this when doing SSR.
If you use Lerna for locally testing your React library, you might add "react/react-dom/react-router" as peerDependencies on your library, but you shouldn't add them as devDependencies. (this makes it duplicate)
You can use node --preserve-symlinks so it can lookup to the parent repo which install the peerDependencies.
For jest, you need to add the parent repo path to "jest.moduleDirectories" option so Jest can resolve them

@apieceofbart it works for me, thx!

I ran into this error when loading external React components, for example with JS modules. They are completely outside the project, loaded with dynamic import() (well jsonp for more support). To get around the issue, we pass/inject React as a property to each module. This works, but then each module is dependent on the containing React app. We're trying to remove dependencies.

As others have mentioned, this property eliminates React as usable for any type of micro front end. The issue is that there is this side-effect of creating global state in the JS runtime when importing React to use as a library.

I understand that for simple use cases, having that side-effect means that React is "easier to use." But when a web page is composed of multiple scripts from multiple bundle steps, one of them should be able to set up React (do that side effect explicitly) and the others can simply call library functions.

I still have an issue with react-bootstrap: #react-bootstrap/react-bootstrap#3965

For anyone else stuck trying to make a lib for React, try https://github.com/whitecolor/yalc it works much better than symlinks.

@mpeyper works well. Thx

@apieceofbart That worked for me. Thanks for the suggestion. ๐Ÿ‘

For me this problem was caused when I navigated to my project directory in PowerShell and didn't capitalize a directory name in the path. I created the project in users/โ€ฆ/โ€ฆ instead of Users/โ€ฆ/...
The issue was resolved when I fixed capitalization errors and recreated the project.

For me, doing:

    "react": "file:../my-library/node_modules/react",
    "react-dom": "file:../my-library/node_modules/react-dom",

Fixed most of it but wasn't enough, I kept getting the error hooks can only be called inside the body of a function component.

Turns out it was because some of my library's components where exported like so:

export default Radium(withTranslation()(MyComponent))

โŒ

Where withTranslation is the HOC from react-i18next and Radium is the HOC from Radium.

And exporting them like so:

export default withTranslation()(Radium(MyComponent))

โœ”๏ธ
Fixed everything.

Note that react-i18next was in version 10 which uses React Hooks

cpuy commented

@mikeaustin We're having the same issue. Do you have any example of "passing/injecting React as a property to each module" ?

Still getting this issue, tried all steps:

  • yarn workspaces (via lerna)
  • checked if there is one react dep, yes

Some things that can have impact:

  • using webpack with libraryTarget
  • using typescript
$ npm ls react-dom
/xxx
โ””โ”€โ”€ react-dom@16.8.6

$ npm ls react
/xxx
โ””โ”€โ”€ react-dom@16.8.6

root package.json

{
  ...
  "workspaces": [
    "packages/*",
  ],
  "devDependencies": {
    ...
  },
  "dependencies": {
    "react": "16.8.6",
    "react-dom": "16.8.6"
  },
  "resolutions": {
    "react": "16.8.6",
    "react-dom": "16.8.6",
    "**/react": "16.8.6",
    "**/react-dom": "16.8.6"
  }
}

@JeremyGrieshop I had a same issue and that worked for me, Thanks.

Add "prestart" in your package.json like below:


"scripts": {
    "prestart": "rimraf ./node_modules/<my package>/node_modules",
    "start": "react-scripts start",
    "build": "react-scripts build",
  },

I had this issue and it wasnt due to multiple versions of react / react-dom
In the custom tooling I use, the require cache was being cleared out (for a purpose outside of this discussion), eg:

Object.keys(require.cache).forEach((key) => {
      delete require.cache[key];
    });

So fyi to people out there, if you are doing something like this - it will effect React Hooks, so avoid it if you can

I had the same issue and I resolved it by adding:

 alias: {
        react: path.resolve('./node_modules/react')
      }

to resolve property in webpack config of my main app.

It's was obviously my mistake of using two copies of React but I agree that it would be great if the error message was better. I think this is maybe similar to: #2402

For anyone out there using Parcel, if dist is where your compiled files are, you ought to add:

  "alias": {
    "react-mediator": "./dist"
  },

To your package.json and then you can still link (npm or yarn) the library for local development/testing.

@mikeaustin We're having the same issue. Do you have any example of "passing/injecting React as a property to each module" ?

You can use React Context to pass around "React" itself, and create a HoC to inject the property into every component. You can pass anything in api like api.React, but it gets a little tricky wrapping HoCs around those components (because now they are only available inside a component, not available to export.

const withReact = Component = props => (
  <ReactContext.Provider value={api => <Component api={api} {...props} /> } />
)

I spend couple of hours on this, if changing the source code to alter the error message and adding more info to it is not easy and you need more time, at least add this note into the doc,

p.s: react has great documentation, but I think that page needs review.