Remove legacy hydration mode (#28440)
While Meta is still using legacy mode and we can't remove completely, Meta is not using legacy hydration so we should be able to remove that. This is just the first step. Once removed, we can vastly simplify the DOMConfig for hydration. This will have to be rebased when tests are upgraded.
Sebastian Markbåge committed
Mar 26, 2024 at 14:41 UTC
670d61bea23470e980ba13c1c8441e375779b0b8
20 files changed
+34
-675
packages/react-dom-bindings/src/client/ReactDOMComponent.js
+4
-15
@@ -330,7 +330,6 @@ function normalizeMarkupForTextOrAttribute(markup: mixed): string {
330
export function checkForUnmatchedText(
331
serverText: string,
332
clientText: string | number | bigint,
333
- isConcurrentMode: boolean,
333
shouldWarnDev: boolean,
334
) {
335
const normalizedClientText = normalizeMarkupForTextOrAttribute(clientText);
@@ -352,7 +351,7 @@ export function checkForUnmatchedText(
351
}
352
}
353
355
- if (isConcurrentMode && enableClientRenderFallbackOnTextMismatch) {
354
+ if (enableClientRenderFallbackOnTextMismatch) {
355
// In concurrent roots, we throw when there's a text mismatch and revert to
356
// client rendering, up to the nearest Suspense boundary.
357
throw new Error('Text content does not match server-rendered HTML.');
@@ -2746,7 +2745,6 @@ export function diffHydratedProperties(
2745
domElement: Element,
2746
tag: string,
2747
props: Object,
2749
- isConcurrentMode: boolean,
2748
shouldWarnDev: boolean,
2749
hostContext: HostContext,
2750
): void {
@@ -2865,14 +2863,9 @@ export function diffHydratedProperties(
2863
// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
2864
if (domElement.textContent !== '' + children) {
2865
if (props.suppressHydrationWarning !== true) {
2868
- checkForUnmatchedText(
2869
- domElement.textContent,
2870
- children,
2871
- isConcurrentMode,
2872
- shouldWarnDev,
2873
- );
2866
+ checkForUnmatchedText(domElement.textContent, children, shouldWarnDev);
2867
}
2875
- if (!isConcurrentMode || !enableClientRenderFallbackOnTextMismatch) {
2868
+ if (!enableClientRenderFallbackOnTextMismatch) {
2869
// We really should be patching this in the commit phase but since
2870
// this only affects legacy mode hydration which is deprecated anyway
2871
// we can get away with it.
@@ -2941,11 +2934,7 @@ export function diffHydratedProperties(
2934
}
2935
}
2936
2944
-export function diffHydratedText(
2945
- textNode: Text,
2946
- text: string,
2947
- isConcurrentMode: boolean,
2948
-): boolean {
2937
+export function diffHydratedText(textNode: Text, text: string): boolean {
2938
const isDifferent = textNode.nodeValue !== text;
2939
return isDifferent;
2940
}
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+12
-52
@@ -30,8 +30,6 @@ import type {
30
import {NotPending} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
31
import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext';
32
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
33
-// TODO: Remove this deep import when we delete the legacy root API
34
-import {ConcurrentMode, NoMode} from 'react-reconciler/src/ReactTypeOfMode';
33
34
import hasOwnProperty from 'shared/hasOwnProperty';
35
import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';
@@ -1370,19 +1368,7 @@ export function hydrateInstance(
1368
// get attached.
1369
updateFiberProps(instance, props);
1370
1373
- // TODO: Temporary hack to check if we're in a concurrent root. We can delete
1374
- // when the legacy root API is removed.
1375
- const isConcurrentMode =
1376
- ((internalInstanceHandle: Fiber).mode & ConcurrentMode) !== NoMode;
1377
-
1378
- diffHydratedProperties(
1379
- instance,
1380
- type,
1381
- props,
1382
- isConcurrentMode,
1383
- shouldWarnDev,
1384
- hostContext,
1385
- );
1371
+ diffHydratedProperties(instance, type, props, shouldWarnDev, hostContext);
1372
}
1373
1374
export function validateHydratableTextInstance(
@@ -1407,12 +1393,7 @@ export function hydrateTextInstance(
1393
): boolean {
1394
precacheFiberNode(internalInstanceHandle, textInstance);
1395
1410
- // TODO: Temporary hack to check if we're in a concurrent root. We can delete
1411
- // when the legacy root API is removed.
1412
- const isConcurrentMode =
1413
- ((internalInstanceHandle: Fiber).mode & ConcurrentMode) !== NoMode;
1414
-
1415
- return diffHydratedText(textInstance, text, isConcurrentMode);
1396
+ return diffHydratedText(textInstance, text);
1397
}
1398
1399
export function hydrateSuspenseInstance(
@@ -1508,15 +1489,9 @@ export function didNotMatchHydratedContainerTextInstance(
1489
parentContainer: Container,
1490
textInstance: TextInstance,
1491
text: string,
1511
- isConcurrentMode: boolean,
1492
shouldWarnDev: boolean,
1493
) {
1514
- checkForUnmatchedText(
1515
- textInstance.nodeValue,
1516
- text,
1517
- isConcurrentMode,
1518
- shouldWarnDev,
1519
- );
1494
+ checkForUnmatchedText(textInstance.nodeValue, text, shouldWarnDev);
1495
}
1496
1497
export function didNotMatchHydratedTextInstance(
@@ -1525,16 +1500,10 @@ export function didNotMatchHydratedTextInstance(
1500
parentInstance: Instance,
1501
textInstance: TextInstance,
1502
text: string,
1528
- isConcurrentMode: boolean,
1503
shouldWarnDev: boolean,
1504
) {
1505
if (parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
1532
- checkForUnmatchedText(
1533
- textInstance.nodeValue,
1534
- text,
1535
- isConcurrentMode,
1536
- shouldWarnDev,
1537
- );
1506
+ checkForUnmatchedText(textInstance.nodeValue, text, shouldWarnDev);
1507
}
1508
}
1509
@@ -1577,17 +1546,14 @@ export function didNotHydrateInstance(
1546
parentProps: Props,
1547
parentInstance: Instance,
1548
instance: HydratableInstance,
1580
- isConcurrentMode: boolean,
1549
) {
1550
if (__DEV__) {
1583
- if (isConcurrentMode || parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
1584
- if (instance.nodeType === ELEMENT_NODE) {
1585
- warnForDeletedHydratableElement(parentInstance, (instance: any));
1586
- } else if (instance.nodeType === COMMENT_NODE) {
1587
- // TODO: warnForDeletedHydratableSuspenseBoundary
1588
- } else {
1589
- warnForDeletedHydratableText(parentInstance, (instance: any));
1590
- }
1551
+ if (instance.nodeType === ELEMENT_NODE) {
1552
+ warnForDeletedHydratableElement(parentInstance, (instance: any));
1553
+ } else if (instance.nodeType === COMMENT_NODE) {
1554
+ // TODO: warnForDeletedHydratableSuspenseBoundary
1555
+ } else {
1556
+ warnForDeletedHydratableText(parentInstance, (instance: any));
1557
}
1558
}
1559
}
@@ -1658,12 +1624,9 @@ export function didNotFindHydratableInstance(
1624
parentInstance: Instance,
1625
type: string,
1626
props: Props,
1661
- isConcurrentMode: boolean,
1627
) {
1628
if (__DEV__) {
1664
- if (isConcurrentMode || parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
1665
- warnForInsertedHydratedElement(parentInstance, type, props);
1666
- }
1629
+ warnForInsertedHydratedElement(parentInstance, type, props);
1630
}
1631
}
1632
@@ -1672,12 +1635,9 @@ export function didNotFindHydratableTextInstance(
1635
parentProps: Props,
1636
parentInstance: Instance,
1637
text: string,
1675
- isConcurrentMode: boolean,
1638
) {
1639
if (__DEV__) {
1678
- if (isConcurrentMode || parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
1679
- warnForInsertedHydratedText(parentInstance, text);
1680
- }
1640
+ warnForInsertedHydratedText(parentInstance, text);
1641
}
1642
}
1643
packages/react-dom/index.classic.fb.js
-1
@@ -24,7 +24,6 @@ export {
24
hydrateRoot,
25
findDOMNode,
26
flushSync,
27
- hydrate,
27
render,
28
unmountComponentAtNode,
29
unstable_batchedUpdates,
packages/react-dom/index.js
-1
@@ -16,7 +16,6 @@ export {
16
hydrateRoot,
17
findDOMNode,
18
flushSync,
19
- hydrate,
19
render,
20
unmountComponentAtNode,
21
unstable_batchedUpdates,
packages/react-dom/index.stable.js
-1
@@ -14,7 +14,6 @@ export {
14
hydrateRoot,
15
findDOMNode,
16
flushSync,
17
- hydrate,
17
render,
18
unmountComponentAtNode,
19
unstable_batchedUpdates,
packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js
-73
@@ -12,7 +12,6 @@
12
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
13
14
let React;
15
-let ReactDOM;
15
let ReactDOMClient;
16
let ReactDOMServer;
17
@@ -453,75 +452,3 @@ describe('ReactDOMServerIntegration', () => {
452
));
453
});
454
});
456
-
457
-describe('ReactDOMServerIntegration (legacy)', () => {
458
- function initModules() {
459
- // Reset warning cache.
460
- jest.resetModules();
461
-
462
- React = require('react');
463
- ReactDOM = require('react-dom');
464
- ReactDOMServer = require('react-dom/server');
465
-
466
- // Make them available to the helpers.
467
- return {
468
- ReactDOM,
469
- ReactDOMServer,
470
- };
471
- }
472
-
473
- const {resetModules, expectMarkupMatch} =
474
- ReactDOMServerIntegrationUtils(initModules);
475
-
476
- beforeEach(() => {
477
- resetModules();
478
- });
479
-
480
- // @gate !disableLegacyMode
481
- it('legacy mode can explicitly ignore errors reconnecting different element types of children', () =>
482
- expectMarkupMatch(
483
- <div>
484
- <div />
485
- </div>,
486
- <div suppressHydrationWarning={true}>
487
- <span />
488
- </div>,
489
- ));
490
-
491
- // @gate !disableLegacyMode
492
- it('legacy mode can explicitly ignore reconnecting more children', () =>
493
- expectMarkupMatch(
494
- <div>
495
- <div />
496
- </div>,
497
- <div suppressHydrationWarning={true}>
498
- <div />
499
- <div />
500
- </div>,
501
- ));
502
-
503
- // @gate !disableLegacyMode
504
- it('legacy mode can explicitly ignore reconnecting fewer children', () =>
505
- expectMarkupMatch(
506
- <div>
507
- <div />
508
- <div />
509
- </div>,
510
- <div suppressHydrationWarning={true}>
511
- <div />
512
- </div>,
513
- ));
514
-
515
- // @gate !disableLegacyMode
516
- it('legacy mode can explicitly ignore reconnecting reordered children', () =>
517
- expectMarkupMatch(
518
- <div suppressHydrationWarning={true}>
519
- <div />
520
- <span />
521
- </div>,
522
- <div suppressHydrationWarning={true}>
523
- <span />
524
- <div />
525
- </div>,
526
- ));
527
-});
packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
-69
@@ -1487,75 +1487,6 @@ describe('ReactDOMServerPartialHydration', () => {
1487
expect(deleted.length).toBe(1);
1488
});
1489
1490
- // @gate !disableLegacyMode
1491
- it('warns and replaces the boundary content in legacy mode', async () => {
1492
- let suspend = false;
1493
- let resolve;
1494
- const promise = new Promise(resolvePromise => (resolve = resolvePromise));
1495
- const ref = React.createRef();
1496
-
1497
- function Child() {
1498
- if (suspend) {
1499
- throw promise;
1500
- } else {
1501
- return 'Hello';
1502
- }
1503
- }
1504
-
1505
- function App() {
1506
- return (
1507
- <div>
1508
- <Suspense fallback="Loading...">
1509
- <span ref={ref}>
1510
- <Child />
1511
- </span>
1512
- </Suspense>
1513
- </div>
1514
- );
1515
- }
1516
-
1517
- // Don't suspend on the server.
1518
- suspend = false;
1519
- const finalHTML = ReactDOMServer.renderToString(<App />);
1520
-
1521
- const container = document.createElement('div');
1522
- container.innerHTML = finalHTML;
1523
-
1524
- const span = container.getElementsByTagName('span')[0];
1525
-
1526
- // On the client we try to hydrate.
1527
- suspend = true;
1528
- await expect(async () => {
1529
- await act(() => {
1530
- ReactDOM.hydrate(<App />, container);
1531
- });
1532
- }).toErrorDev(
1533
- 'Warning: Cannot hydrate Suspense in legacy mode. Switch from ' +
1534
- 'ReactDOM.hydrate(element, container) to ' +
1535
- 'ReactDOMClient.hydrateRoot(container, <App />)' +
1536
- '.render(element) or remove the Suspense components from the server ' +
1537
- 'rendered components.' +
1538
- '\n in Suspense (at **)' +
1539
- '\n in div (at **)' +
1540
- '\n in App (at **)',
1541
- );
1542
-
1543
- // We're now in loading state.
1544
- expect(container.textContent).toBe('Loading...');
1545
-
1546
- const span2 = container.getElementsByTagName('span')[0];
1547
- // This is a new node.
1548
- expect(span).not.toBe(span2);
1549
- expect(ref.current).toBe(null);
1550
-
1551
- // Resolving the promise should render the final content.
1552
- suspend = false;
1553
- await act(() => resolve());
1554
-
1555
- // We should now have hydrated with a ref on the existing span.
1556
- expect(container.textContent).toBe('Hello');
1557
- });
1558
-
1490
it('can insert siblings before the dehydrated boundary', async () => {
1491
let suspend = false;
1492
const promise = new Promise(() => {});
packages/react-dom/src/__tests__/ReactLegacyMount-test.js
-55
@@ -13,10 +13,7 @@ const {COMMENT_NODE} = require('react-dom-bindings/src/client/HTMLNodeType');
13
14
let React;
15
let ReactDOM;
16
-let ReactDOMServer;
17
-let Scheduler;
16
let ReactDOMClient;
19
-let assertLog;
17
let waitForAll;
18
19
describe('ReactMount', () => {
@@ -26,11 +23,8 @@ describe('ReactMount', () => {
23
React = require('react');
24
ReactDOM = require('react-dom');
25
ReactDOMClient = require('react-dom/client');
29
- ReactDOMServer = require('react-dom/server');
30
- Scheduler = require('scheduler');
26
27
const InternalTestUtils = require('internal-test-utils');
33
- assertLog = InternalTestUtils.assertLog;
28
waitForAll = InternalTestUtils.waitForAll;
29
});
30
@@ -138,25 +132,6 @@ describe('ReactMount', () => {
132
expect(instance1 === instance2).toBe(true);
133
});
134
141
- // @gate !disableLegacyMode
142
- it('does not warn if mounting into left padded rendered markup', () => {
143
- const container = document.createElement('container');
144
- container.innerHTML = ReactDOMServer.renderToString(<div />) + ' ';
145
-
146
- // This should probably ideally warn but we ignore extra markup at the root.
147
- ReactDOM.hydrate(<div />, container);
148
- });
149
-
150
- // @gate !disableLegacyMode
151
- it('should warn if mounting into right padded rendered markup', () => {
152
- const container = document.createElement('container');
153
- container.innerHTML = ' ' + ReactDOMServer.renderToString(<div />);
154
-
155
- expect(() => ReactDOM.hydrate(<div />, container)).toErrorDev(
156
- 'Did not expect server HTML to contain the text node " " in <container>.',
157
- );
158
- });
159
-
135
// @gate !disableLegacyMode
136
it('should not warn if mounting into non-empty node', () => {
137
const container = document.createElement('container');
@@ -174,25 +149,6 @@ describe('ReactMount', () => {
149
ReactDOM.render(<div />, iFrame.contentDocument.body);
150
});
151
177
- // @gate !disableLegacyMode
178
- it('should account for escaping on a checksum mismatch', () => {
179
- const div = document.createElement('div');
180
- const markup = ReactDOMServer.renderToString(
181
- <div>This markup contains an nbsp entity: server text</div>,
182
- );
183
- div.innerHTML = markup;
184
-
185
- expect(() =>
186
- ReactDOM.hydrate(
187
- <div>This markup contains an nbsp entity: client text</div>,
188
- div,
189
- ),
190
- ).toErrorDev(
191
- 'Server: "This markup contains an nbsp entity: server text" ' +
192
- 'Client: "This markup contains an nbsp entity: client text"',
193
- );
194
- });
195
-
152
// @gate !disableLegacyMode
153
it('should warn if render removes React-rendered children', () => {
154
const container = document.createElement('container');
@@ -413,17 +369,6 @@ describe('ReactMount', () => {
369
expect(container.textContent).toEqual('Bye');
370
});
371
416
- // @gate !disableLegacyMode
417
- it('callback passed to legacy hydrate() API', () => {
418
- const container = document.createElement('div');
419
- container.innerHTML = '<div>Hi</div>';
420
- ReactDOM.hydrate(<div>Hi</div>, container, () => {
421
- Scheduler.log('callback');
422
- });
423
- expect(container.textContent).toEqual('Hi');
424
- assertLog(['callback']);
425
- });
426
-
372
// @gate !disableLegacyMode
373
it('warns when unmounting with legacy API (no previous content)', async () => {
374
const container = document.createElement('div');
packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js
-15
@@ -26,19 +26,4 @@ describe('ReactDOMRoot', () => {
26
);
27
}
28
});
29
-
30
- // @gate !disableLegacyMode
31
- test('deprecation warning for ReactDOM.hydrate', () => {
32
- spyOnDev(console, 'error');
33
-
34
- container.innerHTML = 'Hi';
35
- ReactDOM.hydrate('Hi', container);
36
- expect(container.textContent).toEqual('Hi');
37
- if (__DEV__) {
38
- expect(console.error).toHaveBeenCalledTimes(1);
39
- expect(console.error.mock.calls[0][0]).toContain(
40
- 'ReactDOM.hydrate has not been supported since React 18',
41
- );
42
- }
43
- });
29
});
packages/react-dom/src/__tests__/ReactRenderDocument-test.js
-18
@@ -345,23 +345,5 @@ describe('rendering React components at document', () => {
345
]);
346
expect(testDocument.body.innerHTML).toBe('Hello world');
347
});
348
-
349
- // @gate !disableLegacyMode
350
- it('supports findDOMNode on full-page components in legacy mode', () => {
351
- const tree = (
352
- <html>
353
- <head>
354
- <title>Hello World</title>
355
- </head>
356
- <body>Hello world</body>
357
- </html>
358
- );
359
-
360
- const markup = ReactDOMServer.renderToString(tree);
361
- const testDocument = getTestDocument(markup);
362
- const component = ReactDOM.hydrate(tree, testDocument);
363
- expect(testDocument.body.innerHTML).toBe('Hello world');
364
- expect(ReactDOM.findDOMNode(component).tagName).toBe('HTML');
365
- });
348
});
349
});
packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
-68
@@ -505,74 +505,6 @@ describe('ReactDOMServerHydration', () => {
505
await act(() => root.render(<div />));
506
});
507
508
- // @gate !disableLegacyMode
509
- it('Suspense + hydration in legacy mode', () => {
510
- const element = document.createElement('div');
511
- element.innerHTML = '<div><div>Hello World</div></div>';
512
- const div = element.firstChild.firstChild;
513
- const ref = React.createRef();
514
- expect(() =>
515
- ReactDOM.hydrate(
516
- <div>
517
- <React.Suspense fallback={null}>
518
- <div ref={ref}>Hello World</div>
519
- </React.Suspense>
520
- </div>,
521
- element,
522
- ),
523
- ).toErrorDev(
524
- 'Warning: Did not expect server HTML to contain a <div> in <div>.',
525
- );
526
-
527
- // The content should've been client rendered and replaced the
528
- // existing div.
529
- expect(ref.current).not.toBe(div);
530
- // The HTML should be the same though.
531
- expect(element.innerHTML).toBe('<div><div>Hello World</div></div>');
532
- });
533
-
534
- // @gate !disableLegacyMode
535
- it('Suspense + hydration in legacy mode (at root)', () => {
536
- const element = document.createElement('div');
537
- element.innerHTML = '<div>Hello World</div>';
538
- const div = element.firstChild;
539
- const ref = React.createRef();
540
- ReactDOM.hydrate(
541
- <React.Suspense fallback={null}>
542
- <div ref={ref}>Hello World</div>
543
- </React.Suspense>,
544
- element,
545
- );
546
-
547
- // The content should've been client rendered.
548
- expect(ref.current).not.toBe(div);
549
- // Unfortunately, since we don't delete the tail at the root, a duplicate will remain.
550
- expect(element.innerHTML).toBe(
551
- '<div>Hello World</div><div>Hello World</div>',
552
- );
553
- });
554
-
555
- // @gate !disableLegacyMode
556
- it('Suspense + hydration in legacy mode with no fallback', () => {
557
- const element = document.createElement('div');
558
- element.innerHTML = '<div>Hello World</div>';
559
- const div = element.firstChild;
560
- const ref = React.createRef();
561
- ReactDOM.hydrate(
562
- <React.Suspense>
563
- <div ref={ref}>Hello World</div>
564
- </React.Suspense>,
565
- element,
566
- );
567
-
568
- // The content should've been client rendered.
569
- expect(ref.current).not.toBe(div);
570
- // Unfortunately, since we don't delete the tail at the root, a duplicate will remain.
571
- expect(element.innerHTML).toBe(
572
- '<div>Hello World</div><div>Hello World</div>',
573
- );
574
- });
575
-
508
// regression test for https://github.com/facebook/react/issues/17170
509
it('should not warn if dangerouslySetInnerHtml=undefined', async () => {
510
const domElement = document.createElement('div');
packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js
+5
-9
@@ -52,15 +52,11 @@ module.exports = function (initModules) {
52
async function asyncReactDOMRender(reactElement, domElement, forceHydrate) {
53
if (forceHydrate) {
54
await act(() => {
55
- if (ReactDOMClient) {
56
- ReactDOMClient.hydrateRoot(domElement, reactElement, {
57
- onRecoverableError: () => {
58
- // TODO: assert on recoverable error count.
59
- },
60
- });
61
- } else {
62
- ReactDOM.hydrate(reactElement, domElement);
63
- }
55
+ ReactDOMClient.hydrateRoot(domElement, reactElement, {
56
+ onRecoverableError: () => {
57
+ // TODO: assert on recoverable error count.
58
+ },
59
+ });
60
});
61
} else {
62
await act(() => {
packages/react-dom/src/client/ReactDOM.js
-2
@@ -21,7 +21,6 @@ import type {
21
import {
22
findDOMNode,
23
render,
24
- hydrate,
24
unstable_renderSubtreeIntoContainer,
25
unmountComponentAtNode,
26
} from './ReactDOMLegacy';
@@ -172,7 +171,6 @@ export {
171
ReactVersion as version,
172
// Disabled behind disableLegacyReactDOMAPIs
173
findDOMNode,
175
- hydrate,
174
render,
175
unmountComponentAtNode,
176
// exposeConcurrentModeAPIs
packages/react-dom/src/client/ReactDOMLegacy.js
-48
@@ -260,54 +260,6 @@ export function findDOMNode(
260
return findHostInstance(componentOrElement);
261
}
262
263
-export function hydrate(
264
- element: React$Node,
265
- container: Container,
266
- callback: ?Function,
267
-): React$Component<any, any> | PublicInstance | null {
268
- if (disableLegacyMode) {
269
- if (__DEV__) {
270
- console.error(
271
- 'ReactDOM.hydrate was removed in React 19. Use hydrateRoot instead',
272
- );
273
- }
274
- throw new Error('ReactDOM: Unsupported Legacy Mode API.');
275
- }
276
- if (__DEV__) {
277
- console.error(
278
- 'ReactDOM.hydrate has not been supported since React 18. Use hydrateRoot ' +
279
- 'instead. Until you switch to the new API, your app will behave as ' +
280
- "if it's running React 17. Learn " +
281
- 'more: https://react.dev/link/switch-to-createroot',
282
- );
283
- }
284
-
285
- if (!isValidContainerLegacy(container)) {
286
- throw new Error('Target container is not a DOM element.');
287
- }
288
-
289
- if (__DEV__) {
290
- const isModernRoot =
291
- isContainerMarkedAsRoot(container) &&
292
- container._reactRootContainer === undefined;
293
- if (isModernRoot) {
294
- console.error(
295
- 'You are calling ReactDOM.hydrate() on a container that was previously ' +
296
- 'passed to ReactDOMClient.createRoot(). This is not supported. ' +
297
- 'Did you mean to call hydrateRoot(container, element)?',
298
- );
299
- }
300
- }
301
- // TODO: throw or warn if we couldn't hydrate?
302
- return legacyRenderSubtreeIntoContainer(
303
- null,
304
- element,
305
- container,
306
- true,
307
- callback,
308
- );
309
-}
310
-
263
export function render(
264
element: React$Element<any>,
265
container: Container,
packages/react-dom/unstable_testing.classic.fb.js
-1
@@ -11,7 +11,6 @@ export {
11
createPortal,
12
findDOMNode,
13
flushSync,
14
- hydrate,
14
render,
15
unmountComponentAtNode,
16
unstable_batchedUpdates,
packages/react-dom/unstable_testing.js
-1
@@ -11,7 +11,6 @@ export {
11
createPortal,
12
findDOMNode,
13
flushSync,
14
- hydrate,
14
render,
15
unmountComponentAtNode,
16
unstable_batchedUpdates,
packages/react-dom/unstable_testing.stable.js
-1
@@ -11,7 +11,6 @@ export {
11
createPortal,
12
findDOMNode,
13
flushSync,
14
- hydrate,
14
render,
15
unmountComponentAtNode,
16
unstable_batchedUpdates,
packages/react-reconciler/src/ReactFiber.js
-6
@@ -849,12 +849,6 @@ export function createFiberFromText(
849
return fiber;
850
}
851
852
-export function createFiberFromHostInstanceForDeletion(): Fiber {
853
- const fiber = createFiber(HostComponent, null, null, NoMode);
854
- fiber.elementType = 'DELETED';
855
- return fiber;
856
-}
857
-
852
export function createFiberFromDehydratedFragment(
853
dehydratedNode: SuspenseInstance,
854
): Fiber {
packages/react-reconciler/src/ReactFiberBeginWork.js
+1
-22
@@ -141,7 +141,6 @@ import {
141
import {
142
NoLane,
143
NoLanes,
144
- SyncLane,
144
OffscreenLane,
145
DefaultHydrationLane,
146
SomeRetryLane,
@@ -2743,18 +2742,7 @@ function mountDehydratedSuspenseComponent(
2742
): null | Fiber {
2743
// During the first pass, we'll bail out and not drill into the children.
2744
// Instead, we'll leave the content in place and try to hydrate it later.
2746
- if ((workInProgress.mode & ConcurrentMode) === NoMode) {
2747
- if (__DEV__) {
2748
- console.error(
2749
- 'Cannot hydrate Suspense in legacy mode. Switch from ' +
2750
- 'ReactDOM.hydrate(element, container) to ' +
2751
- 'ReactDOMClient.hydrateRoot(container, <App />)' +
2752
- '.render(element) or remove the Suspense components from ' +
2753
- 'the server rendered components.',
2754
- );
2755
- }
2756
- workInProgress.lanes = laneToLanes(SyncLane);
2757
- } else if (isSuspenseInstanceFallback(suspenseInstance)) {
2745
+ if (isSuspenseInstanceFallback(suspenseInstance)) {
2746
// This is a client-only boundary. Since we won't get any content from the server
2747
// for this, we need to schedule that at a higher priority based on when it would
2748
// have timed out. In theory we could render it in this pass but it would have the
@@ -2794,15 +2782,6 @@ function updateDehydratedSuspenseComponent(
2782
// but after we've already committed once.
2783
warnIfHydrating();
2784
2797
- if ((workInProgress.mode & ConcurrentMode) === NoMode) {
2798
- return retrySuspenseComponentWithoutHydrating(
2799
- current,
2800
- workInProgress,
2801
- renderLanes,
2802
- null,
2803
- );
2804
- }
2805
-
2785
if (isSuspenseInstanceFallback(suspenseInstance)) {
2786
// This boundary is in a permanent fallback state. In this case, we'll never
2787
// get an update and we'll never be able to hydrate the final content. Let's just try the
packages/react-reconciler/src/ReactFiberHydrationContext.js
+12
-217
@@ -8,7 +8,6 @@
8
*/
9
10
import type {Fiber} from './ReactInternalTypes';
11
-import {NoMode, ConcurrentMode} from './ReactTypeOfMode';
11
import type {
12
Instance,
13
TextInstance,
@@ -28,19 +27,9 @@ import {
27
HostRoot,
28
SuspenseComponent,
29
} from './ReactWorkTags';
31
-import {
32
- ChildDeletion,
33
- Placement,
34
- Hydrating,
35
- NoFlags,
36
- DidCapture,
37
-} from './ReactFiberFlags';
30
import {enableClientRenderFallbackOnTextMismatch} from 'shared/ReactFeatureFlags';
31
40
-import {
41
- createFiberFromHostInstanceForDeletion,
42
- createFiberFromDehydratedFragment,
43
-} from './ReactFiber';
32
+import {createFiberFromDehydratedFragment} from './ReactFiber';
33
import {
34
shouldSetTextContent,
35
supportsHydration,
@@ -168,14 +157,11 @@ function warnUnhydratedInstance(
157
}
158
case HostSingleton:
159
case HostComponent: {
171
- const isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode;
160
didNotHydrateInstance(
161
returnFiber.type,
162
returnFiber.memoizedProps,
163
returnFiber.stateNode,
164
instance,
177
- // TODO: Delete this argument when we remove the legacy root API.
178
- isConcurrentMode,
165
);
166
break;
167
}
@@ -192,23 +178,6 @@ function warnUnhydratedInstance(
178
}
179
}
180
195
-function deleteHydratableInstance(
196
- returnFiber: Fiber,
197
- instance: HydratableInstance,
198
-) {
199
- const childToDelete = createFiberFromHostInstanceForDeletion();
200
- childToDelete.stateNode = instance;
201
- childToDelete.return = returnFiber;
202
-
203
- const deletions = returnFiber.deletions;
204
- if (deletions === null) {
205
- returnFiber.deletions = [childToDelete];
206
- returnFiber.flags |= ChildDeletion;
207
- } else {
208
- deletions.push(childToDelete);
209
- }
210
-}
211
-
181
function warnNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
182
if (__DEV__) {
183
if (didSuspendOrErrorDEV) {
@@ -257,30 +226,22 @@ function warnNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
226
case HostComponent: {
227
const type = fiber.type;
228
const props = fiber.pendingProps;
260
- const isConcurrentMode =
261
- (returnFiber.mode & ConcurrentMode) !== NoMode;
229
didNotFindHydratableInstance(
230
parentType,
231
parentProps,
232
parentInstance,
233
type,
234
props,
268
- // TODO: Delete this argument when we remove the legacy root API.
269
- isConcurrentMode,
235
);
236
break;
237
}
238
case HostText: {
239
const text = fiber.pendingProps;
275
- const isConcurrentMode =
276
- (returnFiber.mode & ConcurrentMode) !== NoMode;
240
didNotFindHydratableTextInstance(
241
parentType,
242
parentProps,
243
parentInstance,
244
text,
282
- // TODO: Delete this argument when we remove the legacy root API.
283
- isConcurrentMode,
245
);
246
break;
247
}
@@ -330,9 +291,6 @@ function warnNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
291
}
292
}
293
}
333
-function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
334
- fiber.flags = (fiber.flags & ~Hydrating) | Placement;
335
-}
294
295
function tryHydrateInstance(fiber: Fiber, nextInstance: any) {
296
// fiber is a HostComponent Fiber
@@ -400,13 +358,6 @@ function tryHydrateSuspense(fiber: Fiber, nextInstance: any) {
358
return false;
359
}
360
403
-function shouldClientRenderOnMismatch(fiber: Fiber) {
404
- return (
405
- (fiber.mode & ConcurrentMode) !== NoMode &&
406
- (fiber.flags & DidCapture) === NoFlags
407
- );
408
-}
409
-
361
function throwOnHydrationMismatch(fiber: Fiber) {
362
throw new Error(
363
'Hydration failed because the initial UI does not match what was ' +
@@ -447,60 +398,12 @@ function tryToClaimNextHydratableInstance(fiber: Fiber): void {
398
currentHostContext,
399
);
400
450
- const initialInstance = nextHydratableInstance;
401
const nextInstance = nextHydratableInstance;
452
- if (!nextInstance) {
453
- if (shouldClientRenderOnMismatch(fiber)) {
454
- if (shouldKeepWarning) {
455
- warnNonHydratedInstance((hydrationParentFiber: any), fiber);
456
- }
457
- throwOnHydrationMismatch(fiber);
458
- }
459
- // Nothing to hydrate. Make it an insertion.
460
- insertNonHydratedInstance((hydrationParentFiber: any), fiber);
402
+ if (!nextInstance || !tryHydrateInstance(fiber, nextInstance)) {
403
if (shouldKeepWarning) {
404
warnNonHydratedInstance((hydrationParentFiber: any), fiber);
405
}
464
- isHydrating = false;
465
- hydrationParentFiber = fiber;
466
- nextHydratableInstance = initialInstance;
467
- return;
468
- }
469
- const firstAttemptedInstance = nextInstance;
470
- if (!tryHydrateInstance(fiber, nextInstance)) {
471
- if (shouldClientRenderOnMismatch(fiber)) {
472
- if (shouldKeepWarning) {
473
- warnNonHydratedInstance((hydrationParentFiber: any), fiber);
474
- }
475
- throwOnHydrationMismatch(fiber);
476
- }
477
- // If we can't hydrate this instance let's try the next one.
478
- // We use this as a heuristic. It's based on intuition and not data so it
479
- // might be flawed or unnecessary.
480
- nextHydratableInstance = getNextHydratableSibling(nextInstance);
481
- const prevHydrationParentFiber: Fiber = (hydrationParentFiber: any);
482
- if (
483
- !nextHydratableInstance ||
484
- !tryHydrateInstance(fiber, nextHydratableInstance)
485
- ) {
486
- // Nothing to hydrate. Make it an insertion.
487
- insertNonHydratedInstance((hydrationParentFiber: any), fiber);
488
- if (shouldKeepWarning) {
489
- warnNonHydratedInstance((hydrationParentFiber: any), fiber);
490
- }
491
- isHydrating = false;
492
- hydrationParentFiber = fiber;
493
- nextHydratableInstance = initialInstance;
494
- return;
495
- }
496
- // We matched the next one, we'll now assume that the first one was
497
- // superfluous and we'll delete it. Since we can't eagerly delete it
498
- // we'll have to schedule a deletion. To do that, this node needs a dummy
499
- // fiber associated with it.
500
- if (shouldKeepWarning) {
501
- warnUnhydratedInstance(prevHydrationParentFiber, firstAttemptedInstance);
502
- }
503
- deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance);
406
+ throwOnHydrationMismatch(fiber);
407
}
408
}
409
@@ -515,63 +418,12 @@ function tryToClaimNextHydratableTextInstance(fiber: Fiber): void {
418
const currentHostContext = getHostContext();
419
shouldKeepWarning = validateHydratableTextInstance(text, currentHostContext);
420
518
- const initialInstance = nextHydratableInstance;
421
const nextInstance = nextHydratableInstance;
520
- if (!nextInstance) {
521
- // We exclude non hydrabable text because we know there are no matching hydratables.
522
- // We either throw or insert depending on the render mode.
523
- if (shouldClientRenderOnMismatch(fiber)) {
524
- if (shouldKeepWarning) {
525
- warnNonHydratedInstance((hydrationParentFiber: any), fiber);
526
- }
527
- throwOnHydrationMismatch(fiber);
528
- }
529
- // Nothing to hydrate. Make it an insertion.
530
- insertNonHydratedInstance((hydrationParentFiber: any), fiber);
422
+ if (!nextInstance || !tryHydrateText(fiber, nextInstance)) {
423
if (shouldKeepWarning) {
424
warnNonHydratedInstance((hydrationParentFiber: any), fiber);
425
}
534
- isHydrating = false;
535
- hydrationParentFiber = fiber;
536
- nextHydratableInstance = initialInstance;
537
- return;
538
- }
539
- const firstAttemptedInstance = nextInstance;
540
- if (!tryHydrateText(fiber, nextInstance)) {
541
- if (shouldClientRenderOnMismatch(fiber)) {
542
- if (shouldKeepWarning) {
543
- warnNonHydratedInstance((hydrationParentFiber: any), fiber);
544
- }
545
- throwOnHydrationMismatch(fiber);
546
- }
547
- // If we can't hydrate this instance let's try the next one.
548
- // We use this as a heuristic. It's based on intuition and not data so it
549
- // might be flawed or unnecessary.
550
- nextHydratableInstance = getNextHydratableSibling(nextInstance);
551
- const prevHydrationParentFiber: Fiber = (hydrationParentFiber: any);
552
-
553
- if (
554
- !nextHydratableInstance ||
555
- !tryHydrateText(fiber, nextHydratableInstance)
556
- ) {
557
- // Nothing to hydrate. Make it an insertion.
558
- insertNonHydratedInstance((hydrationParentFiber: any), fiber);
559
- if (shouldKeepWarning) {
560
- warnNonHydratedInstance((hydrationParentFiber: any), fiber);
561
- }
562
- isHydrating = false;
563
- hydrationParentFiber = fiber;
564
- nextHydratableInstance = initialInstance;
565
- return;
566
- }
567
- // We matched the next one, we'll now assume that the first one was
568
- // superfluous and we'll delete it. Since we can't eagerly delete it
569
- // we'll have to schedule a deletion. To do that, this node needs a dummy
570
- // fiber associated with it.
571
- if (shouldKeepWarning) {
572
- warnUnhydratedInstance(prevHydrationParentFiber, firstAttemptedInstance);
573
- }
574
- deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance);
426
+ throwOnHydrationMismatch(fiber);
427
}
428
}
429
@@ -579,51 +431,10 @@ function tryToClaimNextHydratableSuspenseInstance(fiber: Fiber): void {
431
if (!isHydrating) {
432
return;
433
}
582
- const initialInstance = nextHydratableInstance;
434
const nextInstance = nextHydratableInstance;
584
- if (!nextInstance) {
585
- if (shouldClientRenderOnMismatch(fiber)) {
586
- warnNonHydratedInstance((hydrationParentFiber: any), fiber);
587
- throwOnHydrationMismatch(fiber);
588
- }
589
- // Nothing to hydrate. Make it an insertion.
590
- insertNonHydratedInstance((hydrationParentFiber: any), fiber);
435
+ if (!nextInstance || !tryHydrateSuspense(fiber, nextInstance)) {
436
warnNonHydratedInstance((hydrationParentFiber: any), fiber);
592
- isHydrating = false;
593
- hydrationParentFiber = fiber;
594
- nextHydratableInstance = initialInstance;
595
- return;
596
- }
597
- const firstAttemptedInstance = nextInstance;
598
- if (!tryHydrateSuspense(fiber, nextInstance)) {
599
- if (shouldClientRenderOnMismatch(fiber)) {
600
- warnNonHydratedInstance((hydrationParentFiber: any), fiber);
601
- throwOnHydrationMismatch(fiber);
602
- }
603
- // If we can't hydrate this instance let's try the next one.
604
- // We use this as a heuristic. It's based on intuition and not data so it
605
- // might be flawed or unnecessary.
606
- nextHydratableInstance = getNextHydratableSibling(nextInstance);
607
- const prevHydrationParentFiber: Fiber = (hydrationParentFiber: any);
608
-
609
- if (
610
- !nextHydratableInstance ||
611
- !tryHydrateSuspense(fiber, nextHydratableInstance)
612
- ) {
613
- // Nothing to hydrate. Make it an insertion.
614
- insertNonHydratedInstance((hydrationParentFiber: any), fiber);
615
- warnNonHydratedInstance((hydrationParentFiber: any), fiber);
616
- isHydrating = false;
617
- hydrationParentFiber = fiber;
618
- nextHydratableInstance = initialInstance;
619
- return;
620
- }
621
- // We matched the next one, we'll now assume that the first one was
622
- // superfluous and we'll delete it. Since we can't eagerly delete it
623
- // we'll have to schedule a deletion. To do that, this node needs a dummy
624
- // fiber associated with it.
625
- warnUnhydratedInstance(prevHydrationParentFiber, firstAttemptedInstance);
626
- deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance);
437
+ throwOnHydrationMismatch(fiber);
438
}
439
}
440
@@ -703,17 +514,13 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
514
switch (returnFiber.tag) {
515
case HostRoot: {
516
const parentContainer = returnFiber.stateNode.containerInfo;
706
- const isConcurrentMode =
707
- (returnFiber.mode & ConcurrentMode) !== NoMode;
517
didNotMatchHydratedContainerTextInstance(
518
parentContainer,
519
textInstance,
520
textContent,
712
- // TODO: Delete this argument when we remove the legacy root API.
713
- isConcurrentMode,
521
shouldWarnIfMismatchDev,
522
);
716
- if (isConcurrentMode && enableClientRenderFallbackOnTextMismatch) {
523
+ if (enableClientRenderFallbackOnTextMismatch) {
524
// In concurrent mode we never update the mismatched text,
525
// even if the error was ignored.
526
return false;
@@ -725,19 +532,15 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
532
const parentType = returnFiber.type;
533
const parentProps = returnFiber.memoizedProps;
534
const parentInstance = returnFiber.stateNode;
728
- const isConcurrentMode =
729
- (returnFiber.mode & ConcurrentMode) !== NoMode;
535
didNotMatchHydratedTextInstance(
536
parentType,
537
parentProps,
538
parentInstance,
539
textInstance,
540
textContent,
736
- // TODO: Delete this argument when we remove the legacy root API.
737
- isConcurrentMode,
541
shouldWarnIfMismatchDev,
542
);
740
- if (isConcurrentMode && enableClientRenderFallbackOnTextMismatch) {
543
+ if (enableClientRenderFallbackOnTextMismatch) {
544
// In concurrent mode we never update the mismatched text,
545
// even if the error was ignored.
546
return false;
@@ -861,18 +664,10 @@ function popHydrationState(fiber: Fiber): boolean {
664
}
665
}
666
if (shouldClear) {
864
- let nextInstance = nextHydratableInstance;
667
+ const nextInstance = nextHydratableInstance;
668
if (nextInstance) {
866
- if (shouldClientRenderOnMismatch(fiber)) {
867
- warnIfUnhydratedTailNodes(fiber);
868
- throwOnHydrationMismatch(fiber);
869
- } else {
870
- while (nextInstance) {
871
- warnUnhydratedInstance(fiber, nextInstance);
872
- deleteHydratableInstance(fiber, nextInstance);
873
- nextInstance = getNextHydratableSibling(nextInstance);
874
- }
875
- }
669
+ warnIfUnhydratedTailNodes(fiber);
670
+ throwOnHydrationMismatch(fiber);
671
}
672
}
673
popToNextHostParent(fiber);