@samitouri / QOS-React-1 / commits / 670d61bea2

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: &nbsp; server text</div>,
182 - );
183 - div.innerHTML = markup;
184 -
185 - expect(() =>
186 - ReactDOM.hydrate(
187 - <div>This markup contains an nbsp entity: &nbsp; 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);