Skip to content

Commit 1780659

Browse files
sebmarkbageacdlite
andauthored
Move createRoot/hydrateRoot to react-dom/client (#23385)
* Move createRoot/hydrateRoot to /client We want these APIs ideally to be imported separately from things you might use in arbitrary components (like flushSync). Those other methods are "isomorphic" to how the ReactDOM tree is rendered. Similar to hooks. E.g. importing flushSync into a component that only uses it on the client should ideally not also pull in the entry client implementation on the server. This also creates a nicer parity with /server where the roots are in a separate entry point. Unfortunately, I can't quite do this yet because we have some legacy APIs that we plan on removing (like findDOMNode) and we also haven't implemented flushSync using a flag like startTransition does yet. Another problem is that we currently encourage these APIs to be aliased by /profiling (or unstable_testing). In the future you don't have to alias them because you can just change your roots to just import those APIs and they'll still work with the isomorphic forms. Although we might also just use export conditions for them. For that all to work, I went with a different strategy for now where the real API is in / but it comes with a warning if you use it. If you instead import /client it disables the warning in a wrapper. That means that if you alias / then import /client that will inturn import the alias and it'll just work. In a future breaking changes (likely when we switch to ESM) we can just remove createRoot/hydrateRoot from / and move away from the aliasing strategy. * Update tests to import from react-dom/client * Fix fixtures * Update warnings * Add test for the warning * Update devtools * Change order of react-dom, react-dom/client alias I think the order matters here. The first one takes precedence. * Require react-dom through client so it can be aliased Co-authored-by: Andrew Clark <git@andrewclark.io>
1 parent 75662d6 commit 1780659

File tree

72 files changed

+648
-356
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+648
-356
lines changed

fixtures/blocks/src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import React from 'react';
9-
import {createRoot} from 'react-dom';
9+
import {createRoot} from 'react-dom/client';
1010
import './index.css';
1111
import Router from './Router';
1212

fixtures/concurrent/time-slicing/src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, {PureComponent, unstable_startTransition} from 'react';
2-
import {createRoot} from 'react-dom';
2+
import {createRoot} from 'react-dom/client';
33
import _ from 'lodash';
44
import Charts from './Charts';
55
import Clock from './Clock';

fixtures/ssr/src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import {hydrateRoot} from 'react-dom';
2+
import {hydrateRoot} from 'react-dom/client';
33

44
import App from './components/App';
55

fixtures/ssr/yarn.lock

+9-1
Original file line numberDiff line numberDiff line change
@@ -4265,7 +4265,7 @@ longest@^1.0.1:
42654265
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
42664266
integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=
42674267

4268-
loose-envify@^1.0.0:
4268+
loose-envify@^1.0.0, loose-envify@^1.1.0:
42694269
version "1.4.0"
42704270
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
42714271
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -5945,6 +5945,14 @@ sax@^1.2.1, sax@~1.2.1:
59455945
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
59465946
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
59475947

5948+
scheduler@^0.20.1:
5949+
version "0.20.2"
5950+
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
5951+
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
5952+
dependencies:
5953+
loose-envify "^1.1.0"
5954+
object-assign "^4.1.1"
5955+
59485956
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0:
59495957
version "5.7.1"
59505958
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"

fixtures/ssr2/src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*
77
*/
88

9-
import {hydrateRoot} from 'react-dom';
9+
import {hydrateRoot} from 'react-dom/client';
1010
import App from './App';
1111

1212
hydrateRoot(document, <App assets={window.assetManifest} />);

packages/react-devtools-core/src/standalone.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ import {createElement} from 'react';
1111
import {
1212
// $FlowFixMe Flow does not yet know about flushSync()
1313
flushSync,
14-
// $FlowFixMe Flow does not yet know about createRoot()
15-
createRoot,
16-
} from 'react-dom';
14+
} from 'react-dom/client';
15+
import {createRoot} from 'react-dom/client';
1716
import Bridge from 'react-devtools-shared/src/bridge';
1817
import Store from 'react-devtools-shared/src/devtools/store';
1918
import {

packages/react-devtools-core/webpack.standalone.js

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ module.exports = {
6464
react: resolve(builtModulesDir, 'react'),
6565
'react-debug-tools': resolve(builtModulesDir, 'react-debug-tools'),
6666
'react-devtools-feature-flags': resolveFeatureFlags(featureFlagTarget),
67+
'react-dom/client': resolve(builtModulesDir, 'react-dom/client'),
6768
'react-dom': resolve(builtModulesDir, 'react-dom'),
6869
'react-is': resolve(builtModulesDir, 'react-is'),
6970
scheduler: resolve(builtModulesDir, 'scheduler'),

packages/react-devtools-extensions/webpack.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ module.exports = {
7676
react: resolve(builtModulesDir, 'react'),
7777
'react-debug-tools': resolve(builtModulesDir, 'react-debug-tools'),
7878
'react-devtools-feature-flags': resolveFeatureFlags(featureFlagTarget),
79+
'react-dom/client': resolve(builtModulesDir, 'react-dom/client'),
7980
'react-dom': resolve(builtModulesDir, 'react-dom'),
8081
'react-is': resolve(builtModulesDir, 'react-is'),
8182
scheduler: resolve(builtModulesDir, 'scheduler'),

packages/react-devtools-inline/README.md

+3-5
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const contentWindow = iframe.contentWindow;
6060
const DevTools = initialize(contentWindow);
6161
```
6262

63-
<sup>3</sup> Because the DevTools interface makes use of several new React APIs (e.g. suspense, concurrent mode) it should be rendered using either `ReactDOM.createRoot` or `ReactDOM.createSyncRoot`. **It should not be rendered with `ReactDOM.render`.**
63+
<sup>3</sup> Because the DevTools interface makes use of several new React APIs (e.g. suspense, concurrent mode) it should be rendered using `ReactDOMClient.createRoot`. **It should not be rendered with `ReactDOM.render`.**
6464

6565
## Examples
6666

@@ -110,8 +110,7 @@ const DevTools = initializeFrontend(contentWindow);
110110
// as setting the src of the <iframe> would load a new page (without the injected backend).
111111

112112
// <DevTools /> interface can be rendered in the parent window at any time now...
113-
// Be sure to use either ReactDOM.createRoot()
114-
// or ReactDOM.createSyncRoot() to render this component.
113+
// Be sure to use ReactDOMClient.createRoot() to render this component.
115114

116115
// Let the backend know the frontend is ready and listening.
117116
activateBackend(contentWindow);
@@ -154,8 +153,7 @@ const { contentWindow } = iframe;
154153

155154
// Initialize DevTools UI to listen to the iframe.
156155
// This returns a React component we can render anywhere in the main window.
157-
// Be sure to use either ReactDOM.createRoot()
158-
// or ReactDOM.createSyncRoot() to render this component.
156+
// Be sure to use ReactDOMClient.createRoot() to render this component.
159157
const DevTools = initialize(contentWindow);
160158

161159
// Let the backend know to initialize itself.

packages/react-devtools-inline/webpack.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ module.exports = {
5151
externals: {
5252
react: 'react',
5353
'react-dom': 'react-dom',
54+
'react-dom/client': 'react-dom/client',
5455
'react-is': 'react-is',
5556
scheduler: 'scheduler',
5657
},

packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
describe('Timeline profiler', () => {
1313
let React;
14-
let ReactDOM;
14+
let ReactDOMClient;
1515
let Scheduler;
1616
let renderHelper;
1717
let renderRootHelper;
@@ -31,15 +31,15 @@ describe('Timeline profiler', () => {
3131
};
3232
renderRootHelper = element => {
3333
const container = document.createElement('div');
34-
const root = ReactDOM.createRoot(container);
34+
const root = ReactDOMClient.createRoot(container);
3535
root.render(element);
3636
const unmountFn = () => root.unmount();
3737
unmountFns.push(unmountFn);
3838
return unmountFn;
3939
};
4040

4141
React = require('react');
42-
ReactDOM = require('react-dom');
42+
ReactDOMClient = require('react-dom/client');
4343
Scheduler = require('scheduler');
4444

4545
store = global.store;

packages/react-devtools-shared/src/__tests__/console-test.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow
88
*/
99
let React;
10-
let ReactDOM;
10+
let ReactDOMClient;
1111
let act;
1212
let fakeConsole;
1313
let legacyRender;
@@ -53,7 +53,7 @@ describe('console', () => {
5353
};
5454

5555
React = require('react');
56-
ReactDOM = require('react-dom');
56+
ReactDOMClient = require('react-dom/client');
5757

5858
const utils = require('./utils');
5959
act = utils.act;
@@ -476,7 +476,7 @@ describe('console', () => {
476476
global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;
477477

478478
const container = document.createElement('div');
479-
const root = ReactDOM.createRoot(container);
479+
const root = ReactDOMClient.createRoot(container);
480480

481481
function App() {
482482
fakeConsole.log('log');
@@ -515,7 +515,7 @@ describe('console', () => {
515515
global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = true;
516516

517517
const container = document.createElement('div');
518-
const root = ReactDOM.createRoot(container);
518+
const root = ReactDOMClient.createRoot(container);
519519

520520
function App() {
521521
fakeConsole.log('log');
@@ -557,7 +557,7 @@ describe('console', () => {
557557
null,
558558
);
559559
const container = document.createElement('div');
560-
const root = ReactDOM.createRoot(container);
560+
const root = ReactDOMClient.createRoot(container);
561561

562562
function App() {
563563
fakeConsole.log('log');
@@ -605,7 +605,7 @@ describe('console', () => {
605605
null,
606606
);
607607
const container = document.createElement('div');
608-
const root = ReactDOM.createRoot(container);
608+
const root = ReactDOMClient.createRoot(container);
609609

610610
function App() {
611611
fakeConsole.log('log');
@@ -639,7 +639,7 @@ describe('console', () => {
639639
global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;
640640

641641
const container = document.createElement('div');
642-
const root = ReactDOM.createRoot(container);
642+
const root = ReactDOMClient.createRoot(container);
643643

644644
const Intermediate = ({children}) => children;
645645
const Parent = ({children}) => (
@@ -719,7 +719,7 @@ describe('console error', () => {
719719
};
720720

721721
React = require('react');
722-
ReactDOM = require('react-dom');
722+
ReactDOMClient = require('react-dom/client');
723723

724724
const utils = require('./utils');
725725
act = utils.act;
@@ -728,7 +728,7 @@ describe('console error', () => {
728728

729729
it('error in console log throws without interfering with logging', () => {
730730
const container = document.createElement('div');
731-
const root = ReactDOM.createRoot(container);
731+
const root = ReactDOMClient.createRoot(container);
732732

733733
function App() {
734734
fakeConsole.log('log');

packages/react-devtools-shared/src/__tests__/inspectedElement-test.js

+8-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type Store from 'react-devtools-shared/src/devtools/store';
1616
describe('InspectedElement', () => {
1717
let React;
1818
let ReactDOM;
19+
let ReactDOMClient;
1920
let PropTypes;
2021
let TestRenderer: ReactTestRenderer;
2122
let bridge: FrontendBridge;
@@ -52,6 +53,7 @@ describe('InspectedElement', () => {
5253

5354
React = require('react');
5455
ReactDOM = require('react-dom');
56+
ReactDOMClient = require('react-dom/client');
5557
PropTypes = require('prop-types');
5658
TestUtilsAct = require('jest-react').act;
5759
TestRenderer = utils.requireTestRenderer();
@@ -506,7 +508,7 @@ describe('InspectedElement', () => {
506508
});
507509

508510
const container = document.createElement('div');
509-
const root = ReactDOM.createRoot(container);
511+
const root = ReactDOMClient.createRoot(container);
510512
await utils.actAsync(() => root.render(<Target a={1} b="abc" />));
511513

512514
expect(targetRenderCount).toBe(1);
@@ -2091,25 +2093,25 @@ describe('InspectedElement', () => {
20912093
expect(inspectedElement.rootType).toMatchInlineSnapshot(`"render()"`);
20922094
});
20932095

2094-
it('should display the root type for ReactDOM.hydrateRoot', async () => {
2096+
it('should display the root type for ReactDOMClient.hydrateRoot', async () => {
20952097
const Example = () => <div />;
20962098

20972099
await utils.actAsync(() => {
20982100
const container = document.createElement('div');
20992101
container.innerHTML = '<div></div>';
2100-
ReactDOM.hydrateRoot(container).render(<Example />);
2102+
ReactDOMClient.hydrateRoot(container).render(<Example />);
21012103
}, false);
21022104

21032105
const inspectedElement = await inspectElementAtIndex(0);
21042106
expect(inspectedElement.rootType).toMatchInlineSnapshot(`"hydrateRoot()"`);
21052107
});
21062108

2107-
it('should display the root type for ReactDOM.createRoot', async () => {
2109+
it('should display the root type for ReactDOMClient.createRoot', async () => {
21082110
const Example = () => <div />;
21092111

21102112
await utils.actAsync(() => {
21112113
const container = document.createElement('div');
2112-
ReactDOM.createRoot(container).render(<Example />);
2114+
ReactDOMClient.createRoot(container).render(<Example />);
21132115
}, false);
21142116

21152117
const inspectedElement = await inspectElementAtIndex(0);
@@ -2133,7 +2135,7 @@ describe('InspectedElement', () => {
21332135

21342136
await utils.actAsync(() => {
21352137
const container = document.createElement('div');
2136-
ReactDOM.createRoot(container).render(<Example />);
2138+
ReactDOMClient.createRoot(container).render(<Example />);
21372139
}, false);
21382140

21392141
shouldThrow = true;

0 commit comments

Comments
 (0)