-
Notifications
You must be signed in to change notification settings - Fork 49.8k
Throw if React and React DOM versions don't match #29236
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
Throw an error during module initialization if the version of the "react-dom" package does not match the version of "react". We used to be more relaxed about this, because the "react" package changed so infrequently. However, we now have many more features that rely on an internal protocol between the two packages, including Hooks, Float, and the compiler runtime. So it's important that both packages are versioned in lockstep. Before this change, a version mismatch would often result in a cryptic internal error with no indication of the root cause. Instead, we will now compare the versions during module initialization and immediately throw an error to catch mistakes as early as possible and provide a clear error message.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| * @flow | ||
| */ | ||
|
|
||
| import reactDOMPackageVersion from 'shared/ReactVersion'; | ||
| import * as IsomorphicReactPackage from 'react'; | ||
|
|
||
| export function ensureCorrectIsomorphicReactVersion() { | ||
| const isomorphicReactPackageVersion = IsomorphicReactPackage.version; | ||
| if (isomorphicReactPackageVersion !== reactDOMPackageVersion) { | ||
| throw new Error( | ||
| 'Incompatible React versions: The "react" and "react-dom" packages must ' + | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a perfect world we'd also detect environment mismatches e.g. importing
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could add another internal field (or check for the presence of a server-only API, and vice versa), but I'm less concerned about that one since it's only the tip of the iceberg of what you have to do to configure a Server Components set-up correctly. For a similar reason I didn't add a check to React Native because nobody really imports the React Native renderer directly; it's configured by some sort of framework.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we actually add it for React Native too? Too often the RN sync are using the wrong versions together, which caused issues when landing breaking changes
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like, RN installs the renderer, but the template still allows using your own version of |
||
| 'have the exact same version. Instead got:\n' + | ||
| ` - react: ${isomorphicReactPackageVersion}\n` + | ||
| ` - react-dom: ${reactDOMPackageVersion}\n` + | ||
| 'Learn more: https://react.dev/warnings/version-mismatch', | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| * @emails react-core | ||
| */ | ||
|
|
||
| 'use strict'; | ||
|
|
||
| describe('ReactMismatchedVersions-test', () => { | ||
| // Polyfills for test environment | ||
| global.ReadableStream = | ||
| require('web-streams-polyfill/ponyfill/es6').ReadableStream; | ||
| global.TextEncoder = require('util').TextEncoder; | ||
|
|
||
| jest.mock('react', () => { | ||
| const actualReact = jest.requireActual('react'); | ||
| return { | ||
| ...actualReact, | ||
| version: '18.0.0-whoa-this-aint-the-right-react', | ||
| __actualVersion: actualReact.version, | ||
| }; | ||
| }); | ||
| const React = require('react'); | ||
| const actualReactVersion = React.__actualVersion; | ||
|
|
||
| test('importing "react-dom/client" throws if version does not match React version', async () => { | ||
| expect(() => require('react-dom/client')).toThrow( | ||
| 'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
| 'must have the exact same version. Instead got:\n' + | ||
| ' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
| ` - react-dom: ${actualReactVersion}`, | ||
| ); | ||
| }); | ||
|
|
||
| // When running in source mode, we lazily require the implementation to | ||
| // simulate the static config dependency injection we do at build time. So it | ||
| // only errors once you call something and trigger the require. Running the | ||
| // test in build mode is sufficient. | ||
| // @gate !source | ||
| test('importing "react-dom/server" throws if version does not match React version', async () => { | ||
| expect(() => require('react-dom/server')).toThrow( | ||
| 'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
| 'must have the exact same version. Instead got:\n' + | ||
| ' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
| ` - react-dom: ${actualReactVersion}`, | ||
| ); | ||
| }); | ||
|
|
||
| // @gate !source | ||
| test('importing "react-dom/server.node" throws if version does not match React version', async () => { | ||
| expect(() => require('react-dom/server.node')).toThrow( | ||
| 'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
| 'must have the exact same version. Instead got:\n' + | ||
| ' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
| ` - react-dom: ${actualReactVersion}`, | ||
| ); | ||
| }); | ||
|
|
||
| // @gate !source | ||
| test('importing "react-dom/server.browser" throws if version does not match React version', async () => { | ||
| expect(() => require('react-dom/server.browser')).toThrow( | ||
| 'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
| 'must have the exact same version. Instead got:\n' + | ||
| ' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
| ` - react-dom: ${actualReactVersion}`, | ||
| ); | ||
| }); | ||
|
|
||
| // @gate !source | ||
| test('importing "react-dom/server.bun" throws if version does not match React version', async () => { | ||
| expect(() => require('react-dom/server.bun')).toThrow( | ||
| 'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
| 'must have the exact same version. Instead got:\n' + | ||
| ' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
| ` - react-dom: ${actualReactVersion}`, | ||
| ); | ||
| }); | ||
|
|
||
| // @gate !source | ||
| test('importing "react-dom/server.edge" throws if version does not match React version', async () => { | ||
| expect(() => require('react-dom/server.edge')).toThrow( | ||
| 'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
| 'must have the exact same version. Instead got:\n' + | ||
| ' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
| ` - react-dom: ${actualReactVersion}`, | ||
| ); | ||
| }); | ||
|
|
||
| test('importing "react-dom/static" throws if version does not match React version', async () => { | ||
| expect(() => require('react-dom/static')).toThrow( | ||
| 'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
| 'must have the exact same version. Instead got:\n' + | ||
| ' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
| ` - react-dom: ${actualReactVersion}`, | ||
| ); | ||
| }); | ||
|
|
||
| test('importing "react-dom/static.node" throws if version does not match React version', async () => { | ||
| expect(() => require('react-dom/static.node')).toThrow( | ||
| 'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
| 'must have the exact same version. Instead got:\n' + | ||
| ' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
| ` - react-dom: ${actualReactVersion}`, | ||
| ); | ||
| }); | ||
|
|
||
| test('importing "react-dom/static.browser" throws if version does not match React version', async () => { | ||
| expect(() => require('react-dom/static.browser')).toThrow( | ||
| 'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
| 'must have the exact same version. Instead got:\n' + | ||
| ' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
| ` - react-dom: ${actualReactVersion}`, | ||
| ); | ||
| }); | ||
|
|
||
| test('importing "react-dom/static.edge" throws if version does not match React version', async () => { | ||
| expect(() => require('react-dom/static.edge')).toThrow( | ||
| 'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
| 'must have the exact same version. Instead got:\n' + | ||
| ' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
| ` - react-dom: ${actualReactVersion}`, | ||
| ); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we compile to CJS this doesn't matter much but it might be nice to
import {version} from "react"so that it doesn't pull in every export and disables dead export elimination.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been doing it this way based on this comment. Not sure if it still applies:
react/packages/react-reconciler/src/Scheduler.js
Lines 10 to 15 in e8b016c