From 670d61bea23470e980ba13c1c8441e375779b0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Tue, 26 Mar 2024 14:41:49 -0700 Subject: [PATCH] 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. --- .../src/client/ReactDOMComponent.js | 19 +- .../src/client/ReactFiberConfigDOM.js | 64 +---- packages/react-dom/index.classic.fb.js | 1 - packages/react-dom/index.js | 1 - packages/react-dom/index.stable.js | 1 - ...ctDOMServerIntegrationReconnecting-test.js | 73 ------ ...DOMServerPartialHydration-test.internal.js | 69 ------ .../src/__tests__/ReactLegacyMount-test.js | 55 ----- .../__tests__/ReactLegacyRootWarnings-test.js | 15 -- .../src/__tests__/ReactRenderDocument-test.js | 18 -- .../ReactServerRenderingHydration-test.js | 68 ------ .../ReactDOMServerIntegrationTestUtils.js | 14 +- packages/react-dom/src/client/ReactDOM.js | 2 - .../react-dom/src/client/ReactDOMLegacy.js | 48 ---- .../react-dom/unstable_testing.classic.fb.js | 1 - packages/react-dom/unstable_testing.js | 1 - packages/react-dom/unstable_testing.stable.js | 1 - packages/react-reconciler/src/ReactFiber.js | 6 - .../src/ReactFiberBeginWork.js | 23 +- .../src/ReactFiberHydrationContext.js | 229 +----------------- 20 files changed, 34 insertions(+), 675 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js index 7071f32b75e48..6067ba010d4f8 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js @@ -330,7 +330,6 @@ function normalizeMarkupForTextOrAttribute(markup: mixed): string { export function checkForUnmatchedText( serverText: string, clientText: string | number | bigint, - isConcurrentMode: boolean, shouldWarnDev: boolean, ) { const normalizedClientText = normalizeMarkupForTextOrAttribute(clientText); @@ -352,7 +351,7 @@ export function checkForUnmatchedText( } } - if (isConcurrentMode && enableClientRenderFallbackOnTextMismatch) { + if (enableClientRenderFallbackOnTextMismatch) { // In concurrent roots, we throw when there's a text mismatch and revert to // client rendering, up to the nearest Suspense boundary. throw new Error('Text content does not match server-rendered HTML.'); @@ -2746,7 +2745,6 @@ export function diffHydratedProperties( domElement: Element, tag: string, props: Object, - isConcurrentMode: boolean, shouldWarnDev: boolean, hostContext: HostContext, ): void { @@ -2865,14 +2863,9 @@ export function diffHydratedProperties( // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint if (domElement.textContent !== '' + children) { if (props.suppressHydrationWarning !== true) { - checkForUnmatchedText( - domElement.textContent, - children, - isConcurrentMode, - shouldWarnDev, - ); + checkForUnmatchedText(domElement.textContent, children, shouldWarnDev); } - if (!isConcurrentMode || !enableClientRenderFallbackOnTextMismatch) { + if (!enableClientRenderFallbackOnTextMismatch) { // We really should be patching this in the commit phase but since // this only affects legacy mode hydration which is deprecated anyway // we can get away with it. @@ -2941,11 +2934,7 @@ export function diffHydratedProperties( } } -export function diffHydratedText( - textNode: Text, - text: string, - isConcurrentMode: boolean, -): boolean { +export function diffHydratedText(textNode: Text, text: string): boolean { const isDifferent = textNode.nodeValue !== text; return isDifferent; } diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 450038eb70dd9..a06f9c00b610f 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -30,8 +30,6 @@ import type { import {NotPending} from 'react-dom-bindings/src/shared/ReactDOMFormActions'; import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext'; import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities'; -// TODO: Remove this deep import when we delete the legacy root API -import {ConcurrentMode, NoMode} from 'react-reconciler/src/ReactTypeOfMode'; import hasOwnProperty from 'shared/hasOwnProperty'; import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion'; @@ -1370,19 +1368,7 @@ export function hydrateInstance( // get attached. updateFiberProps(instance, props); - // TODO: Temporary hack to check if we're in a concurrent root. We can delete - // when the legacy root API is removed. - const isConcurrentMode = - ((internalInstanceHandle: Fiber).mode & ConcurrentMode) !== NoMode; - - diffHydratedProperties( - instance, - type, - props, - isConcurrentMode, - shouldWarnDev, - hostContext, - ); + diffHydratedProperties(instance, type, props, shouldWarnDev, hostContext); } export function validateHydratableTextInstance( @@ -1407,12 +1393,7 @@ export function hydrateTextInstance( ): boolean { precacheFiberNode(internalInstanceHandle, textInstance); - // TODO: Temporary hack to check if we're in a concurrent root. We can delete - // when the legacy root API is removed. - const isConcurrentMode = - ((internalInstanceHandle: Fiber).mode & ConcurrentMode) !== NoMode; - - return diffHydratedText(textInstance, text, isConcurrentMode); + return diffHydratedText(textInstance, text); } export function hydrateSuspenseInstance( @@ -1508,15 +1489,9 @@ export function didNotMatchHydratedContainerTextInstance( parentContainer: Container, textInstance: TextInstance, text: string, - isConcurrentMode: boolean, shouldWarnDev: boolean, ) { - checkForUnmatchedText( - textInstance.nodeValue, - text, - isConcurrentMode, - shouldWarnDev, - ); + checkForUnmatchedText(textInstance.nodeValue, text, shouldWarnDev); } export function didNotMatchHydratedTextInstance( @@ -1525,16 +1500,10 @@ export function didNotMatchHydratedTextInstance( parentInstance: Instance, textInstance: TextInstance, text: string, - isConcurrentMode: boolean, shouldWarnDev: boolean, ) { if (parentProps[SUPPRESS_HYDRATION_WARNING] !== true) { - checkForUnmatchedText( - textInstance.nodeValue, - text, - isConcurrentMode, - shouldWarnDev, - ); + checkForUnmatchedText(textInstance.nodeValue, text, shouldWarnDev); } } @@ -1577,17 +1546,14 @@ export function didNotHydrateInstance( parentProps: Props, parentInstance: Instance, instance: HydratableInstance, - isConcurrentMode: boolean, ) { if (__DEV__) { - if (isConcurrentMode || parentProps[SUPPRESS_HYDRATION_WARNING] !== true) { - if (instance.nodeType === ELEMENT_NODE) { - warnForDeletedHydratableElement(parentInstance, (instance: any)); - } else if (instance.nodeType === COMMENT_NODE) { - // TODO: warnForDeletedHydratableSuspenseBoundary - } else { - warnForDeletedHydratableText(parentInstance, (instance: any)); - } + if (instance.nodeType === ELEMENT_NODE) { + warnForDeletedHydratableElement(parentInstance, (instance: any)); + } else if (instance.nodeType === COMMENT_NODE) { + // TODO: warnForDeletedHydratableSuspenseBoundary + } else { + warnForDeletedHydratableText(parentInstance, (instance: any)); } } } @@ -1658,12 +1624,9 @@ export function didNotFindHydratableInstance( parentInstance: Instance, type: string, props: Props, - isConcurrentMode: boolean, ) { if (__DEV__) { - if (isConcurrentMode || parentProps[SUPPRESS_HYDRATION_WARNING] !== true) { - warnForInsertedHydratedElement(parentInstance, type, props); - } + warnForInsertedHydratedElement(parentInstance, type, props); } } @@ -1672,12 +1635,9 @@ export function didNotFindHydratableTextInstance( parentProps: Props, parentInstance: Instance, text: string, - isConcurrentMode: boolean, ) { if (__DEV__) { - if (isConcurrentMode || parentProps[SUPPRESS_HYDRATION_WARNING] !== true) { - warnForInsertedHydratedText(parentInstance, text); - } + warnForInsertedHydratedText(parentInstance, text); } } diff --git a/packages/react-dom/index.classic.fb.js b/packages/react-dom/index.classic.fb.js index d8814243b690d..b5e974f2b5f25 100644 --- a/packages/react-dom/index.classic.fb.js +++ b/packages/react-dom/index.classic.fb.js @@ -24,7 +24,6 @@ export { hydrateRoot, findDOMNode, flushSync, - hydrate, render, unmountComponentAtNode, unstable_batchedUpdates, diff --git a/packages/react-dom/index.js b/packages/react-dom/index.js index 4db349ea5bf0d..0c55c3a92219a 100644 --- a/packages/react-dom/index.js +++ b/packages/react-dom/index.js @@ -16,7 +16,6 @@ export { hydrateRoot, findDOMNode, flushSync, - hydrate, render, unmountComponentAtNode, unstable_batchedUpdates, diff --git a/packages/react-dom/index.stable.js b/packages/react-dom/index.stable.js index 36f5563fda5da..5e5a5fe275d95 100644 --- a/packages/react-dom/index.stable.js +++ b/packages/react-dom/index.stable.js @@ -14,7 +14,6 @@ export { hydrateRoot, findDOMNode, flushSync, - hydrate, render, unmountComponentAtNode, unstable_batchedUpdates, diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js index 3eae27486a097..a58f7f8529f98 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js @@ -12,7 +12,6 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils'); let React; -let ReactDOM; let ReactDOMClient; let ReactDOMServer; @@ -453,75 +452,3 @@ describe('ReactDOMServerIntegration', () => { )); }); }); - -describe('ReactDOMServerIntegration (legacy)', () => { - function initModules() { - // Reset warning cache. - jest.resetModules(); - - React = require('react'); - ReactDOM = require('react-dom'); - ReactDOMServer = require('react-dom/server'); - - // Make them available to the helpers. - return { - ReactDOM, - ReactDOMServer, - }; - } - - const {resetModules, expectMarkupMatch} = - ReactDOMServerIntegrationUtils(initModules); - - beforeEach(() => { - resetModules(); - }); - - // @gate !disableLegacyMode - it('legacy mode can explicitly ignore errors reconnecting different element types of children', () => - expectMarkupMatch( -
-
-
, -
- -
, - )); - - // @gate !disableLegacyMode - it('legacy mode can explicitly ignore reconnecting more children', () => - expectMarkupMatch( -
-
-
, -
-
-
-
, - )); - - // @gate !disableLegacyMode - it('legacy mode can explicitly ignore reconnecting fewer children', () => - expectMarkupMatch( -
-
-
-
, -
-
-
, - )); - - // @gate !disableLegacyMode - it('legacy mode can explicitly ignore reconnecting reordered children', () => - expectMarkupMatch( -
-
- -
, -
- -
-
, - )); -}); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index 6cdd82fefb011..c8ce39f86e3bf 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -1487,75 +1487,6 @@ describe('ReactDOMServerPartialHydration', () => { expect(deleted.length).toBe(1); }); - // @gate !disableLegacyMode - it('warns and replaces the boundary content in legacy mode', async () => { - let suspend = false; - let resolve; - const promise = new Promise(resolvePromise => (resolve = resolvePromise)); - const ref = React.createRef(); - - function Child() { - if (suspend) { - throw promise; - } else { - return 'Hello'; - } - } - - function App() { - return ( -
- - - - - -
- ); - } - - // Don't suspend on the server. - suspend = false; - const finalHTML = ReactDOMServer.renderToString(); - - const container = document.createElement('div'); - container.innerHTML = finalHTML; - - const span = container.getElementsByTagName('span')[0]; - - // On the client we try to hydrate. - suspend = true; - await expect(async () => { - await act(() => { - ReactDOM.hydrate(, container); - }); - }).toErrorDev( - 'Warning: Cannot hydrate Suspense in legacy mode. Switch from ' + - 'ReactDOM.hydrate(element, container) to ' + - 'ReactDOMClient.hydrateRoot(container, )' + - '.render(element) or remove the Suspense components from the server ' + - 'rendered components.' + - '\n in Suspense (at **)' + - '\n in div (at **)' + - '\n in App (at **)', - ); - - // We're now in loading state. - expect(container.textContent).toBe('Loading...'); - - const span2 = container.getElementsByTagName('span')[0]; - // This is a new node. - expect(span).not.toBe(span2); - expect(ref.current).toBe(null); - - // Resolving the promise should render the final content. - suspend = false; - await act(() => resolve()); - - // We should now have hydrated with a ref on the existing span. - expect(container.textContent).toBe('Hello'); - }); - it('can insert siblings before the dehydrated boundary', async () => { let suspend = false; const promise = new Promise(() => {}); diff --git a/packages/react-dom/src/__tests__/ReactLegacyMount-test.js b/packages/react-dom/src/__tests__/ReactLegacyMount-test.js index 52a5458697d48..6835e9a018a8f 100644 --- a/packages/react-dom/src/__tests__/ReactLegacyMount-test.js +++ b/packages/react-dom/src/__tests__/ReactLegacyMount-test.js @@ -13,10 +13,7 @@ const {COMMENT_NODE} = require('react-dom-bindings/src/client/HTMLNodeType'); let React; let ReactDOM; -let ReactDOMServer; -let Scheduler; let ReactDOMClient; -let assertLog; let waitForAll; describe('ReactMount', () => { @@ -26,11 +23,8 @@ describe('ReactMount', () => { React = require('react'); ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); - ReactDOMServer = require('react-dom/server'); - Scheduler = require('scheduler'); const InternalTestUtils = require('internal-test-utils'); - assertLog = InternalTestUtils.assertLog; waitForAll = InternalTestUtils.waitForAll; }); @@ -138,25 +132,6 @@ describe('ReactMount', () => { expect(instance1 === instance2).toBe(true); }); - // @gate !disableLegacyMode - it('does not warn if mounting into left padded rendered markup', () => { - const container = document.createElement('container'); - container.innerHTML = ReactDOMServer.renderToString(
) + ' '; - - // This should probably ideally warn but we ignore extra markup at the root. - ReactDOM.hydrate(
, container); - }); - - // @gate !disableLegacyMode - it('should warn if mounting into right padded rendered markup', () => { - const container = document.createElement('container'); - container.innerHTML = ' ' + ReactDOMServer.renderToString(
); - - expect(() => ReactDOM.hydrate(
, container)).toErrorDev( - 'Did not expect server HTML to contain the text node " " in .', - ); - }); - // @gate !disableLegacyMode it('should not warn if mounting into non-empty node', () => { const container = document.createElement('container'); @@ -174,25 +149,6 @@ describe('ReactMount', () => { ReactDOM.render(
, iFrame.contentDocument.body); }); - // @gate !disableLegacyMode - it('should account for escaping on a checksum mismatch', () => { - const div = document.createElement('div'); - const markup = ReactDOMServer.renderToString( -
This markup contains an nbsp entity:   server text
, - ); - div.innerHTML = markup; - - expect(() => - ReactDOM.hydrate( -
This markup contains an nbsp entity:   client text
, - div, - ), - ).toErrorDev( - 'Server: "This markup contains an nbsp entity:   server text" ' + - 'Client: "This markup contains an nbsp entity:   client text"', - ); - }); - // @gate !disableLegacyMode it('should warn if render removes React-rendered children', () => { const container = document.createElement('container'); @@ -413,17 +369,6 @@ describe('ReactMount', () => { expect(container.textContent).toEqual('Bye'); }); - // @gate !disableLegacyMode - it('callback passed to legacy hydrate() API', () => { - const container = document.createElement('div'); - container.innerHTML = '
Hi
'; - ReactDOM.hydrate(
Hi
, container, () => { - Scheduler.log('callback'); - }); - expect(container.textContent).toEqual('Hi'); - assertLog(['callback']); - }); - // @gate !disableLegacyMode it('warns when unmounting with legacy API (no previous content)', async () => { const container = document.createElement('div'); diff --git a/packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js b/packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js index 35d63da06b48b..1eef3d140e325 100644 --- a/packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js +++ b/packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js @@ -26,19 +26,4 @@ describe('ReactDOMRoot', () => { ); } }); - - // @gate !disableLegacyMode - test('deprecation warning for ReactDOM.hydrate', () => { - spyOnDev(console, 'error'); - - container.innerHTML = 'Hi'; - ReactDOM.hydrate('Hi', container); - expect(container.textContent).toEqual('Hi'); - if (__DEV__) { - expect(console.error).toHaveBeenCalledTimes(1); - expect(console.error.mock.calls[0][0]).toContain( - 'ReactDOM.hydrate has not been supported since React 18', - ); - } - }); }); diff --git a/packages/react-dom/src/__tests__/ReactRenderDocument-test.js b/packages/react-dom/src/__tests__/ReactRenderDocument-test.js index b7347470a6656..f719be6981f10 100644 --- a/packages/react-dom/src/__tests__/ReactRenderDocument-test.js +++ b/packages/react-dom/src/__tests__/ReactRenderDocument-test.js @@ -345,23 +345,5 @@ describe('rendering React components at document', () => { ]); expect(testDocument.body.innerHTML).toBe('Hello world'); }); - - // @gate !disableLegacyMode - it('supports findDOMNode on full-page components in legacy mode', () => { - const tree = ( - - - Hello World - - Hello world - - ); - - const markup = ReactDOMServer.renderToString(tree); - const testDocument = getTestDocument(markup); - const component = ReactDOM.hydrate(tree, testDocument); - expect(testDocument.body.innerHTML).toBe('Hello world'); - expect(ReactDOM.findDOMNode(component).tagName).toBe('HTML'); - }); }); }); diff --git a/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js b/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js index 3ca77a661594b..e28296348afa7 100644 --- a/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js @@ -505,74 +505,6 @@ describe('ReactDOMServerHydration', () => { await act(() => root.render(
)); }); - // @gate !disableLegacyMode - it('Suspense + hydration in legacy mode', () => { - const element = document.createElement('div'); - element.innerHTML = '
Hello World
'; - const div = element.firstChild.firstChild; - const ref = React.createRef(); - expect(() => - ReactDOM.hydrate( -
- -
Hello World
-
-
, - element, - ), - ).toErrorDev( - 'Warning: Did not expect server HTML to contain a
in
.', - ); - - // The content should've been client rendered and replaced the - // existing div. - expect(ref.current).not.toBe(div); - // The HTML should be the same though. - expect(element.innerHTML).toBe('
Hello World
'); - }); - - // @gate !disableLegacyMode - it('Suspense + hydration in legacy mode (at root)', () => { - const element = document.createElement('div'); - element.innerHTML = '
Hello World
'; - const div = element.firstChild; - const ref = React.createRef(); - ReactDOM.hydrate( - -
Hello World
-
, - element, - ); - - // The content should've been client rendered. - expect(ref.current).not.toBe(div); - // Unfortunately, since we don't delete the tail at the root, a duplicate will remain. - expect(element.innerHTML).toBe( - '
Hello World
Hello World
', - ); - }); - - // @gate !disableLegacyMode - it('Suspense + hydration in legacy mode with no fallback', () => { - const element = document.createElement('div'); - element.innerHTML = '
Hello World
'; - const div = element.firstChild; - const ref = React.createRef(); - ReactDOM.hydrate( - -
Hello World
-
, - element, - ); - - // The content should've been client rendered. - expect(ref.current).not.toBe(div); - // Unfortunately, since we don't delete the tail at the root, a duplicate will remain. - expect(element.innerHTML).toBe( - '
Hello World
Hello World
', - ); - }); - // regression test for https://github.com/facebook/react/issues/17170 it('should not warn if dangerouslySetInnerHtml=undefined', async () => { const domElement = document.createElement('div'); diff --git a/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js b/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js index e9c4479b028fb..015942131d01d 100644 --- a/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js +++ b/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js @@ -52,15 +52,11 @@ module.exports = function (initModules) { async function asyncReactDOMRender(reactElement, domElement, forceHydrate) { if (forceHydrate) { await act(() => { - if (ReactDOMClient) { - ReactDOMClient.hydrateRoot(domElement, reactElement, { - onRecoverableError: () => { - // TODO: assert on recoverable error count. - }, - }); - } else { - ReactDOM.hydrate(reactElement, domElement); - } + ReactDOMClient.hydrateRoot(domElement, reactElement, { + onRecoverableError: () => { + // TODO: assert on recoverable error count. + }, + }); }); } else { await act(() => { diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js index 78fffeb41cb34..9f9ea849fd66d 100644 --- a/packages/react-dom/src/client/ReactDOM.js +++ b/packages/react-dom/src/client/ReactDOM.js @@ -21,7 +21,6 @@ import type { import { findDOMNode, render, - hydrate, unstable_renderSubtreeIntoContainer, unmountComponentAtNode, } from './ReactDOMLegacy'; @@ -172,7 +171,6 @@ export { ReactVersion as version, // Disabled behind disableLegacyReactDOMAPIs findDOMNode, - hydrate, render, unmountComponentAtNode, // exposeConcurrentModeAPIs diff --git a/packages/react-dom/src/client/ReactDOMLegacy.js b/packages/react-dom/src/client/ReactDOMLegacy.js index 64fef73640b10..50cb6fbf94b10 100644 --- a/packages/react-dom/src/client/ReactDOMLegacy.js +++ b/packages/react-dom/src/client/ReactDOMLegacy.js @@ -260,54 +260,6 @@ export function findDOMNode( return findHostInstance(componentOrElement); } -export function hydrate( - element: React$Node, - container: Container, - callback: ?Function, -): React$Component | PublicInstance | null { - if (disableLegacyMode) { - if (__DEV__) { - console.error( - 'ReactDOM.hydrate was removed in React 19. Use hydrateRoot instead', - ); - } - throw new Error('ReactDOM: Unsupported Legacy Mode API.'); - } - if (__DEV__) { - console.error( - 'ReactDOM.hydrate has not been supported since React 18. Use hydrateRoot ' + - 'instead. Until you switch to the new API, your app will behave as ' + - "if it's running React 17. Learn " + - 'more: https://react.dev/link/switch-to-createroot', - ); - } - - if (!isValidContainerLegacy(container)) { - throw new Error('Target container is not a DOM element.'); - } - - if (__DEV__) { - const isModernRoot = - isContainerMarkedAsRoot(container) && - container._reactRootContainer === undefined; - if (isModernRoot) { - console.error( - 'You are calling ReactDOM.hydrate() on a container that was previously ' + - 'passed to ReactDOMClient.createRoot(). This is not supported. ' + - 'Did you mean to call hydrateRoot(container, element)?', - ); - } - } - // TODO: throw or warn if we couldn't hydrate? - return legacyRenderSubtreeIntoContainer( - null, - element, - container, - true, - callback, - ); -} - export function render( element: React$Element, container: Container, diff --git a/packages/react-dom/unstable_testing.classic.fb.js b/packages/react-dom/unstable_testing.classic.fb.js index 6dd68f3669dc3..9958b22cc3277 100644 --- a/packages/react-dom/unstable_testing.classic.fb.js +++ b/packages/react-dom/unstable_testing.classic.fb.js @@ -11,7 +11,6 @@ export { createPortal, findDOMNode, flushSync, - hydrate, render, unmountComponentAtNode, unstable_batchedUpdates, diff --git a/packages/react-dom/unstable_testing.js b/packages/react-dom/unstable_testing.js index 9729a427e4aa6..080b7340d11b2 100644 --- a/packages/react-dom/unstable_testing.js +++ b/packages/react-dom/unstable_testing.js @@ -11,7 +11,6 @@ export { createPortal, findDOMNode, flushSync, - hydrate, render, unmountComponentAtNode, unstable_batchedUpdates, diff --git a/packages/react-dom/unstable_testing.stable.js b/packages/react-dom/unstable_testing.stable.js index 453e8b4fdece7..855f6f1fc006a 100644 --- a/packages/react-dom/unstable_testing.stable.js +++ b/packages/react-dom/unstable_testing.stable.js @@ -11,7 +11,6 @@ export { createPortal, findDOMNode, flushSync, - hydrate, render, unmountComponentAtNode, unstable_batchedUpdates, diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 194bababd644d..ce909b802530a 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -849,12 +849,6 @@ export function createFiberFromText( return fiber; } -export function createFiberFromHostInstanceForDeletion(): Fiber { - const fiber = createFiber(HostComponent, null, null, NoMode); - fiber.elementType = 'DELETED'; - return fiber; -} - export function createFiberFromDehydratedFragment( dehydratedNode: SuspenseInstance, ): Fiber { diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 0b788996bd10b..28e04f7c4b870 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -141,7 +141,6 @@ import { import { NoLane, NoLanes, - SyncLane, OffscreenLane, DefaultHydrationLane, SomeRetryLane, @@ -2743,18 +2742,7 @@ function mountDehydratedSuspenseComponent( ): null | Fiber { // During the first pass, we'll bail out and not drill into the children. // Instead, we'll leave the content in place and try to hydrate it later. - if ((workInProgress.mode & ConcurrentMode) === NoMode) { - if (__DEV__) { - console.error( - 'Cannot hydrate Suspense in legacy mode. Switch from ' + - 'ReactDOM.hydrate(element, container) to ' + - 'ReactDOMClient.hydrateRoot(container, )' + - '.render(element) or remove the Suspense components from ' + - 'the server rendered components.', - ); - } - workInProgress.lanes = laneToLanes(SyncLane); - } else if (isSuspenseInstanceFallback(suspenseInstance)) { + if (isSuspenseInstanceFallback(suspenseInstance)) { // This is a client-only boundary. Since we won't get any content from the server // for this, we need to schedule that at a higher priority based on when it would // have timed out. In theory we could render it in this pass but it would have the @@ -2794,15 +2782,6 @@ function updateDehydratedSuspenseComponent( // but after we've already committed once. warnIfHydrating(); - if ((workInProgress.mode & ConcurrentMode) === NoMode) { - return retrySuspenseComponentWithoutHydrating( - current, - workInProgress, - renderLanes, - null, - ); - } - if (isSuspenseInstanceFallback(suspenseInstance)) { // This boundary is in a permanent fallback state. In this case, we'll never // get an update and we'll never be able to hydrate the final content. Let's just try the diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.js b/packages/react-reconciler/src/ReactFiberHydrationContext.js index 90d87e13d7b33..11cb591ec9a8b 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.js @@ -8,7 +8,6 @@ */ import type {Fiber} from './ReactInternalTypes'; -import {NoMode, ConcurrentMode} from './ReactTypeOfMode'; import type { Instance, TextInstance, @@ -28,19 +27,9 @@ import { HostRoot, SuspenseComponent, } from './ReactWorkTags'; -import { - ChildDeletion, - Placement, - Hydrating, - NoFlags, - DidCapture, -} from './ReactFiberFlags'; import {enableClientRenderFallbackOnTextMismatch} from 'shared/ReactFeatureFlags'; -import { - createFiberFromHostInstanceForDeletion, - createFiberFromDehydratedFragment, -} from './ReactFiber'; +import {createFiberFromDehydratedFragment} from './ReactFiber'; import { shouldSetTextContent, supportsHydration, @@ -168,14 +157,11 @@ function warnUnhydratedInstance( } case HostSingleton: case HostComponent: { - const isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode; didNotHydrateInstance( returnFiber.type, returnFiber.memoizedProps, returnFiber.stateNode, instance, - // TODO: Delete this argument when we remove the legacy root API. - isConcurrentMode, ); break; } @@ -192,23 +178,6 @@ function warnUnhydratedInstance( } } -function deleteHydratableInstance( - returnFiber: Fiber, - instance: HydratableInstance, -) { - const childToDelete = createFiberFromHostInstanceForDeletion(); - childToDelete.stateNode = instance; - childToDelete.return = returnFiber; - - const deletions = returnFiber.deletions; - if (deletions === null) { - returnFiber.deletions = [childToDelete]; - returnFiber.flags |= ChildDeletion; - } else { - deletions.push(childToDelete); - } -} - function warnNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) { if (__DEV__) { if (didSuspendOrErrorDEV) { @@ -257,30 +226,22 @@ function warnNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) { case HostComponent: { const type = fiber.type; const props = fiber.pendingProps; - const isConcurrentMode = - (returnFiber.mode & ConcurrentMode) !== NoMode; didNotFindHydratableInstance( parentType, parentProps, parentInstance, type, props, - // TODO: Delete this argument when we remove the legacy root API. - isConcurrentMode, ); break; } case HostText: { const text = fiber.pendingProps; - const isConcurrentMode = - (returnFiber.mode & ConcurrentMode) !== NoMode; didNotFindHydratableTextInstance( parentType, parentProps, parentInstance, text, - // TODO: Delete this argument when we remove the legacy root API. - isConcurrentMode, ); break; } @@ -330,9 +291,6 @@ function warnNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) { } } } -function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) { - fiber.flags = (fiber.flags & ~Hydrating) | Placement; -} function tryHydrateInstance(fiber: Fiber, nextInstance: any) { // fiber is a HostComponent Fiber @@ -400,13 +358,6 @@ function tryHydrateSuspense(fiber: Fiber, nextInstance: any) { return false; } -function shouldClientRenderOnMismatch(fiber: Fiber) { - return ( - (fiber.mode & ConcurrentMode) !== NoMode && - (fiber.flags & DidCapture) === NoFlags - ); -} - function throwOnHydrationMismatch(fiber: Fiber) { throw new Error( 'Hydration failed because the initial UI does not match what was ' + @@ -447,60 +398,12 @@ function tryToClaimNextHydratableInstance(fiber: Fiber): void { currentHostContext, ); - const initialInstance = nextHydratableInstance; const nextInstance = nextHydratableInstance; - if (!nextInstance) { - if (shouldClientRenderOnMismatch(fiber)) { - if (shouldKeepWarning) { - warnNonHydratedInstance((hydrationParentFiber: any), fiber); - } - throwOnHydrationMismatch(fiber); - } - // Nothing to hydrate. Make it an insertion. - insertNonHydratedInstance((hydrationParentFiber: any), fiber); + if (!nextInstance || !tryHydrateInstance(fiber, nextInstance)) { if (shouldKeepWarning) { warnNonHydratedInstance((hydrationParentFiber: any), fiber); } - isHydrating = false; - hydrationParentFiber = fiber; - nextHydratableInstance = initialInstance; - return; - } - const firstAttemptedInstance = nextInstance; - if (!tryHydrateInstance(fiber, nextInstance)) { - if (shouldClientRenderOnMismatch(fiber)) { - if (shouldKeepWarning) { - warnNonHydratedInstance((hydrationParentFiber: any), fiber); - } - throwOnHydrationMismatch(fiber); - } - // If we can't hydrate this instance let's try the next one. - // We use this as a heuristic. It's based on intuition and not data so it - // might be flawed or unnecessary. - nextHydratableInstance = getNextHydratableSibling(nextInstance); - const prevHydrationParentFiber: Fiber = (hydrationParentFiber: any); - if ( - !nextHydratableInstance || - !tryHydrateInstance(fiber, nextHydratableInstance) - ) { - // Nothing to hydrate. Make it an insertion. - insertNonHydratedInstance((hydrationParentFiber: any), fiber); - if (shouldKeepWarning) { - warnNonHydratedInstance((hydrationParentFiber: any), fiber); - } - isHydrating = false; - hydrationParentFiber = fiber; - nextHydratableInstance = initialInstance; - return; - } - // We matched the next one, we'll now assume that the first one was - // superfluous and we'll delete it. Since we can't eagerly delete it - // we'll have to schedule a deletion. To do that, this node needs a dummy - // fiber associated with it. - if (shouldKeepWarning) { - warnUnhydratedInstance(prevHydrationParentFiber, firstAttemptedInstance); - } - deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance); + throwOnHydrationMismatch(fiber); } } @@ -515,63 +418,12 @@ function tryToClaimNextHydratableTextInstance(fiber: Fiber): void { const currentHostContext = getHostContext(); shouldKeepWarning = validateHydratableTextInstance(text, currentHostContext); - const initialInstance = nextHydratableInstance; const nextInstance = nextHydratableInstance; - if (!nextInstance) { - // We exclude non hydrabable text because we know there are no matching hydratables. - // We either throw or insert depending on the render mode. - if (shouldClientRenderOnMismatch(fiber)) { - if (shouldKeepWarning) { - warnNonHydratedInstance((hydrationParentFiber: any), fiber); - } - throwOnHydrationMismatch(fiber); - } - // Nothing to hydrate. Make it an insertion. - insertNonHydratedInstance((hydrationParentFiber: any), fiber); + if (!nextInstance || !tryHydrateText(fiber, nextInstance)) { if (shouldKeepWarning) { warnNonHydratedInstance((hydrationParentFiber: any), fiber); } - isHydrating = false; - hydrationParentFiber = fiber; - nextHydratableInstance = initialInstance; - return; - } - const firstAttemptedInstance = nextInstance; - if (!tryHydrateText(fiber, nextInstance)) { - if (shouldClientRenderOnMismatch(fiber)) { - if (shouldKeepWarning) { - warnNonHydratedInstance((hydrationParentFiber: any), fiber); - } - throwOnHydrationMismatch(fiber); - } - // If we can't hydrate this instance let's try the next one. - // We use this as a heuristic. It's based on intuition and not data so it - // might be flawed or unnecessary. - nextHydratableInstance = getNextHydratableSibling(nextInstance); - const prevHydrationParentFiber: Fiber = (hydrationParentFiber: any); - - if ( - !nextHydratableInstance || - !tryHydrateText(fiber, nextHydratableInstance) - ) { - // Nothing to hydrate. Make it an insertion. - insertNonHydratedInstance((hydrationParentFiber: any), fiber); - if (shouldKeepWarning) { - warnNonHydratedInstance((hydrationParentFiber: any), fiber); - } - isHydrating = false; - hydrationParentFiber = fiber; - nextHydratableInstance = initialInstance; - return; - } - // We matched the next one, we'll now assume that the first one was - // superfluous and we'll delete it. Since we can't eagerly delete it - // we'll have to schedule a deletion. To do that, this node needs a dummy - // fiber associated with it. - if (shouldKeepWarning) { - warnUnhydratedInstance(prevHydrationParentFiber, firstAttemptedInstance); - } - deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance); + throwOnHydrationMismatch(fiber); } } @@ -579,51 +431,10 @@ function tryToClaimNextHydratableSuspenseInstance(fiber: Fiber): void { if (!isHydrating) { return; } - const initialInstance = nextHydratableInstance; const nextInstance = nextHydratableInstance; - if (!nextInstance) { - if (shouldClientRenderOnMismatch(fiber)) { - warnNonHydratedInstance((hydrationParentFiber: any), fiber); - throwOnHydrationMismatch(fiber); - } - // Nothing to hydrate. Make it an insertion. - insertNonHydratedInstance((hydrationParentFiber: any), fiber); + if (!nextInstance || !tryHydrateSuspense(fiber, nextInstance)) { warnNonHydratedInstance((hydrationParentFiber: any), fiber); - isHydrating = false; - hydrationParentFiber = fiber; - nextHydratableInstance = initialInstance; - return; - } - const firstAttemptedInstance = nextInstance; - if (!tryHydrateSuspense(fiber, nextInstance)) { - if (shouldClientRenderOnMismatch(fiber)) { - warnNonHydratedInstance((hydrationParentFiber: any), fiber); - throwOnHydrationMismatch(fiber); - } - // If we can't hydrate this instance let's try the next one. - // We use this as a heuristic. It's based on intuition and not data so it - // might be flawed or unnecessary. - nextHydratableInstance = getNextHydratableSibling(nextInstance); - const prevHydrationParentFiber: Fiber = (hydrationParentFiber: any); - - if ( - !nextHydratableInstance || - !tryHydrateSuspense(fiber, nextHydratableInstance) - ) { - // Nothing to hydrate. Make it an insertion. - insertNonHydratedInstance((hydrationParentFiber: any), fiber); - warnNonHydratedInstance((hydrationParentFiber: any), fiber); - isHydrating = false; - hydrationParentFiber = fiber; - nextHydratableInstance = initialInstance; - return; - } - // We matched the next one, we'll now assume that the first one was - // superfluous and we'll delete it. Since we can't eagerly delete it - // we'll have to schedule a deletion. To do that, this node needs a dummy - // fiber associated with it. - warnUnhydratedInstance(prevHydrationParentFiber, firstAttemptedInstance); - deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance); + throwOnHydrationMismatch(fiber); } } @@ -703,17 +514,13 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean { switch (returnFiber.tag) { case HostRoot: { const parentContainer = returnFiber.stateNode.containerInfo; - const isConcurrentMode = - (returnFiber.mode & ConcurrentMode) !== NoMode; didNotMatchHydratedContainerTextInstance( parentContainer, textInstance, textContent, - // TODO: Delete this argument when we remove the legacy root API. - isConcurrentMode, shouldWarnIfMismatchDev, ); - if (isConcurrentMode && enableClientRenderFallbackOnTextMismatch) { + if (enableClientRenderFallbackOnTextMismatch) { // In concurrent mode we never update the mismatched text, // even if the error was ignored. return false; @@ -725,19 +532,15 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean { const parentType = returnFiber.type; const parentProps = returnFiber.memoizedProps; const parentInstance = returnFiber.stateNode; - const isConcurrentMode = - (returnFiber.mode & ConcurrentMode) !== NoMode; didNotMatchHydratedTextInstance( parentType, parentProps, parentInstance, textInstance, textContent, - // TODO: Delete this argument when we remove the legacy root API. - isConcurrentMode, shouldWarnIfMismatchDev, ); - if (isConcurrentMode && enableClientRenderFallbackOnTextMismatch) { + if (enableClientRenderFallbackOnTextMismatch) { // In concurrent mode we never update the mismatched text, // even if the error was ignored. return false; @@ -861,18 +664,10 @@ function popHydrationState(fiber: Fiber): boolean { } } if (shouldClear) { - let nextInstance = nextHydratableInstance; + const nextInstance = nextHydratableInstance; if (nextInstance) { - if (shouldClientRenderOnMismatch(fiber)) { - warnIfUnhydratedTailNodes(fiber); - throwOnHydrationMismatch(fiber); - } else { - while (nextInstance) { - warnUnhydratedInstance(fiber, nextInstance); - deleteHydratableInstance(fiber, nextInstance); - nextInstance = getNextHydratableSibling(nextInstance); - } - } + warnIfUnhydratedTailNodes(fiber); + throwOnHydrationMismatch(fiber); } } popToNextHostParent(fiber);