facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
224.72k stars 45.84k forks source link

Hooks + multiple instances of React #13991

Open brunolemos opened 5 years ago

brunolemos commented 5 years ago

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.

philipp-spiess commented 5 years ago

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.

brunolemos commented 5 years ago

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.

GabrielBB commented 5 years ago

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

mpeyper commented 5 years ago

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"
apieceofbart commented 5 years ago

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: https://github.com/facebook/react/issues/2402

GabrielBB commented 5 years ago

@mpeyper It works. Thanks

jimbo commented 5 years ago

@apieceofbart That worked for me. Thanks for the suggestion. 👍

jbandi commented 5 years ago

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).

vpicone commented 5 years ago

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!

dotlouis commented 5 years ago

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.

doasync commented 5 years ago

I fixed my issue by removing the caret in "react": "^16.7.0-alpha.2" Here is the full comment: https://github.com/facebook/react/issues/14454#issuecomment-449585005

pelotom commented 5 years ago

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"
  },
leecade commented 5 years ago

Same here!!

zbuttram commented 5 years ago

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.

taylorham commented 5 years ago

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!

dotlouis commented 5 years ago

@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 🧐

taylorham commented 5 years ago

@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".

medyll commented 5 years ago

[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)
DylanVann commented 5 years ago

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"
    },
    ...
}
dotlouis commented 5 years ago

@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?

seeden commented 5 years ago

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.

theKashey commented 5 years ago

Just for a better context - https://github.com/facebook/react/issues/14257

dotlouis commented 5 years ago

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?

jmlivingston commented 5 years ago

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"
}
theKashey commented 5 years ago

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

dotlouis commented 5 years ago

@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.

theKashey commented 5 years ago

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.

DylanVann commented 5 years ago

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.

rmolinamir commented 5 years ago

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.

guru-florida commented 5 years ago

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

ghost commented 5 years ago

@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..!

guru-florida commented 5 years ago

@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 5 years ago

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

seeden commented 5 years ago

@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 5 years ago

@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

seeden commented 5 years ago

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

gaearon commented 5 years ago

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.

gaearon commented 5 years ago

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 5 years ago

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

damassi commented 5 years ago

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

adamscybot commented 5 years ago

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?

mike1808 commented 5 years ago

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.

OliverRadini commented 5 years ago

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

tj commented 5 years ago

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

chulanovskyi commented 5 years ago

@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

damiangreen commented 5 years ago

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 (https://github.com/apollographql/react-apollo/issues/2541) Upgrading to 2.3.1 fixed it

JennieJi commented 5 years ago

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:

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.

mwarger commented 5 years ago

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);
taiansu commented 5 years ago

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.

ajcrews commented 5 years ago

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".