Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn when two versions of React are used alongside #2402

Closed
gaearon opened this issue Oct 23, 2014 · 59 comments · Fixed by #3580
Closed

Warn when two versions of React are used alongside #2402

gaearon opened this issue Oct 23, 2014 · 59 comments · Fixed by #3580

Comments

@gaearon
Copy link
Collaborator

gaearon commented Oct 23, 2014

People lose hours of work debugging a simple issue: two versions of React being loaded at the same time.

gaearon/react-hot-loader#32 (comment)
KyleAMathews/coffee-react-quickstart#10 (comment)
gaearon/react-document-title#1 (comment)
clayallsopp/react.backbone#26

Because there is no warning right away when this happens, they usually discover the problem through invariant violations. Some of them are descriptive (e.g. Uncaught Error: Invariant Violation: The handler for Route "hello" must be a valid React component) and I think I even saw warning that said something like "check if two copies of React are loaded", but some are really cryptic: Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs....

Is there a reason why we don't want to warn right away when two copies of React are loaded?

@pluma
Copy link
Contributor

pluma commented Oct 23, 2014

There's probably no good way to find out if two copies of React are loaded (CommonJS modules are the antithesis of sharing a global scope, they're literally two duplicate but separate modules). But I would expect React to be clever enough to figure out that, say, a component is a React component even if it's not from the same copy of React.

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 23, 2014

I think it's lesser evil to put some flag on window, like the flag React reads from devtools, than to spend any more time on this.

@syranide
Copy link
Contributor

@sebmarkbage @zpao This is a commonly recurring problem that is very hard to debug, simply setting window.ReactWasHere = true and testing whether it is set would save a lot of people a lot of headache. Any objections to this? Should be preferable to just extend isValidElement (or w/e) with simple duck-typing that warns if it looks like a ReactElement but isn't... or something like that. It's free and doesn't interfere with intentionally running multiple React instances.

@sebmarkbage
Copy link
Collaborator

I think that in the future, this will actually just work. In the meantime we can warn here: https://github.com/facebook/react/blob/master/src/core/ReactElement.js#L230

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 23, 2014

Would it catch all of the issues listed above though? Usually the problem comes up when some component or helper library loads its own version. Will we get to isValidElement in this case?

Besides, does React truly support independent Reacts on page right now? Do they not interfere in any way at all if they aren't mixed (e.g. browser event handling)?

@gaearon
Copy link
Collaborator Author

gaearon commented Oct 23, 2014

(To clarify, I was referring to @syranide'a comment)

@syranide
Copy link
Contributor

Would it catch all of the issues listed above though? Usually the problem comes up when some component or helper library loads its own version. Will we get to isValidElement in this case?

I might be mistaken, but ignoring weird hacks then the only issue with multiple instances of React are when ReactElements from different instances gets mixed up. Depending on which API you feed them to, you're going to see lots of different errors though. So yeah... I think that should take care of all, or most at the very least.

Besides, does React truly support independent Reacts on page right now? Do they not interfere in any way at all if they aren't mixed (e.g. browser event handling)?

As long as you never stop event propagation or mount them inside each other it should be OK I think (but still not great though).

I think that in the future, this will actually just work. In the meantime we can warn here: https://github.com/facebook/react/blob/master/src/core/ReactElement.js#L230

Good point.

@stefanomasini
Copy link

Just a confirmation from someone who just flushed 3 hours of debugging time down the toilet that, yes, this issue is hard to debug, please make at least a warning.
In my case I was having a weird exception (TypeError: Cannot read property 'firstChild' of undefined) caused by the fact that a component created with a different (uninitialised) copy of React was carrying a _owner = null, thereby breaking findComponentRoot().

@sophiebits
Copy link
Collaborator

We should already be warning in the case that an element from one copy of React is passed to React.render in another:

// Check if it quacks like an element

I guess the cases here are when nesting elements from different versions inside each other?

@stefanomasini
Copy link

Yes. At least it was in my case. Nested elements created from different React versions.

eldh referenced this issue in eldh/react-coffee-elements Jan 6, 2015
Removing react dep to fix issue where multiple react versions would
load.
@joaomilho
Copy link

Just my 2cents: I had the same issue today and my problem was using browserify 8.1.3 instead of 8.1.1

Hope it helps

@charypar
Copy link

The other issue with this is you can't reliably use the dependency injection mechanism, e.g. to change the batching strategy, while using components from multiple modules where each has it's own instance of React as a dependency. Even if they are the same version of React.

@nottoseethesun
Copy link

+1 ; thanks gaearon

@sedge
Copy link

sedge commented Mar 9, 2015

@charypar said: "The other issue with this is you can't reliably use the dependency injection mechanism, e.g. to change the batching strategy, while using components from multiple modules where each has it's own instance of React as a dependency. Even if they are the same version of React."

This.

@aearly
Copy link

aearly commented Mar 9, 2015

The main workaround many modular React components are using is to specify React as a peerDependency -- the parent module must provide React to prevent multiple copies. This is problematic because peerDependencies are on track to become deprecated because they introduce dependency hell.

@JedWatson
Copy link
Contributor

I've seen this a lot as well, and had lots of requests to switch React to a peerDependency (+devDependency) in my packages which I'm currently resisting for reasons @aearly mentioned.

The most common frustration I've seen is when npm subtly installs two versions of React, and Browserify (correctly) includes both in the build. It can happen unexpectedly w/ package updates and breakage ensues. A warning from React that "hey, there is more than one of me on the page" seems like the most elegant way to warn developers early and direct them to an explanation of what's gone wrong / how to fix it.

It's a side-effect of npm's dependency rules and node's require behaviour that I end up with two Reacts when I just want one. Looks like things will get better with npm@3 (see npm/npm#6565) but in the meantime I agree with @gaearon about adding a "lesser evil" hack:

if (__DEV__) {
  var ExecutionEnvironment = require('ExecutionEnvironment');
  if (ExecutionEnvironment.canUseDOM) {
    if (window.__REACT_INITIALISED__) console.warn("Warning: more than one instance of React has been initialised. This may cause unexpected behaviour; see {url}");
    window. __REACT_INITIALISED__ = true;
  }
}

... there's a good argument to be made that this isn't React's problem to solve, but since it's a common problem and React can solve it, I think it would be great if it did.

@sedge
Copy link

sedge commented Mar 16, 2015

@JedWatson A solution I found with browserify was to alias all require() calls like so (example shown with grunt):

    browserify: {
      dev: {
        files: {
          'public/app.js': ['react/index.jsx']
        },
        options: {
          alias: [
            "react:react", "React:react"
          ],
          transform: [babelify,reactify]
        }
      }
    },

so it isn't impossible to fix, but I'd make the argument that since this is such a common issue it needs to be highlighted more clearly in the React docs.

@gaearon
Copy link
Collaborator Author

gaearon commented Apr 2, 2015

@robertknight
Copy link
Contributor

I guess the cases here are when nesting elements from different versions inside each other?

@spicyj when using browserify it can happen just when requiring two different versions of React because of the way it handles deduplication. Requires with the same path and code between the two versions can end up getting de-duped. Modules where the code/require path was changed between versions get deduplicated and others don't.

So I encountered this:

  1. ReactDOMSelect (from React v1) is evaluated and it mixes in ReactBrowserComponentMixin as part of its spec
  2. ReactInjection.Class.injectMixin(ReactBrowserComponentMixin) is evaluated and it adds ReactBrowserComponentMixin to all classes subsequently created by ReactClass.createClass (shared between React v1 and v2 because it didn't change between the two versions of React)
  3. ReactDOMSelect (from React v2) is evaluated and it tries to mix in ReactBrowserComponentMixin twice, once from ReactDOMSelect's own spec and once from the global injected mixin list.

As long as modules can be stateful, this approach to deduplication seems horrifically unsafe. I'll file an issue with Browserify itself, I haven't yet verified if Webpack would have the same issue. Either way - I think it would make sense to handle it specifically in React given how it can trip newcomers up.

robertknight pushed a commit to robertknight/react that referenced this issue Apr 3, 2015
This causes a variety of hard-to-debug issues.
See facebook#2402 for examples.

Fixes facebook#2402
@JohnnyZhao
Copy link

same issue with webpack and react.js loading manually.

@mnquintana
Copy link

@gaearon @spicyj Is it still an issue to load more than one version of React on a page, even if those instances are managing totally separate DOM trees and aren't being explicitly instantiated as globals? Like if, say, I had a package with ComponentA and ComponentB as dependencies, and they both depended on different versions of React, with npm as the dependency manager, could they be used together on one page?

@sophiebits
Copy link
Collaborator

@mnquintana No, that should work now if both are on a recent version of React.

@idhard
Copy link

idhard commented Sep 2, 2016

if both are on a recent version of

Could you please point me out what would be the behavior of React if , let say , we want to introduce a widget including React in a host page that has an older version of it and the possible workaround to make it work?
Thanks

@sophiebits
Copy link
Collaborator

Old versions of React are probably only tripped up by data-reactid IDs in the DOM that it didn't generate. If you're doing only client-side rendering with your widget, it should only have a data-reactroot attribute and no data-reactid so there shouldn't be any conflict.

@jedwards1211
Copy link
Contributor

jedwards1211 commented Sep 14, 2018

Just ran into problems again today when I accidentally had 16.4.2 and 16.5.0 installed in a project, no warning from React.

To make matters worse, the duplicate versions were caused by a bug in yarn upgrade that has been open for over a year...

@OliverRadini
Copy link

Was there any resolution as to whether or not this is possible and/or a good idea? It seems like it'd provide developers with useful information.

@prasanthLalapeta
Copy link

Project A uses React 16 wherein Project B uses React 15, Lets say Project A is package got used in Project B - can that work?

@dgreene1
Copy link

Would Preact and React have the same issue described here?

I’m asking because I’m using single-spa for a platform play at a large company of distributed teams.

@robertknight
Copy link
Contributor

Would Preact and React have the same issue described here?

It sounds to me like there are two separate issues here:

  1. Can you have two different copies of the library loaded and in use anywhere on a page in separate components that don't interact with each other?
  2. Can you have components from two different bundles, each with their own copy of the library, interact with each other (eg. the render function from one library receiving an element created with createElement from the other library)

I don't know about the status with modern versions of React. In the case of Preact (1) should be fine but since it does have some global state you could run into problems in situation (2). Please file an issue in the Preact repository if you'd like to discuss further.

@dkeesey
Copy link

dkeesey commented Feb 23, 2022

I think that in the future, this will actually just work. In the meantime we can warn here: https://github.com/facebook/react/blob/master/src/core/ReactElement.js#L230

This appears to be a bad link.

@IPWright83
Copy link

For someone encountering this at the moment - does anyone have any tips on identifying the cause and debugging the issue? I've used the check from the hooks page (how I discovered this) which confirms I've got 2 instances loaded:

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

I believe I'm doing things by the book, in a monorepo react, react-dom etc are all always peerDependencies. What steps would you recommend after this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet