Skip to content

Commit

Permalink
Remove legacy hydration mode (#28440)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sebmarkbage authored Mar 26, 2024
1 parent dbfbfb3 commit 670d61b
Show file tree
Hide file tree
Showing 20 changed files with 34 additions and 675 deletions.
19 changes: 4 additions & 15 deletions packages/react-dom-bindings/src/client/ReactDOMComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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.');
Expand Down Expand Up @@ -2746,7 +2745,6 @@ export function diffHydratedProperties(
domElement: Element,
tag: string,
props: Object,
isConcurrentMode: boolean,
shouldWarnDev: boolean,
hostContext: HostContext,
): void {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
Expand Down
64 changes: 12 additions & 52 deletions packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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));
}
}
}
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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);
}
}

Expand Down
1 change: 0 additions & 1 deletion packages/react-dom/index.classic.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export {
hydrateRoot,
findDOMNode,
flushSync,
hydrate,
render,
unmountComponentAtNode,
unstable_batchedUpdates,
Expand Down
1 change: 0 additions & 1 deletion packages/react-dom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export {
hydrateRoot,
findDOMNode,
flushSync,
hydrate,
render,
unmountComponentAtNode,
unstable_batchedUpdates,
Expand Down
1 change: 0 additions & 1 deletion packages/react-dom/index.stable.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export {
hydrateRoot,
findDOMNode,
flushSync,
hydrate,
render,
unmountComponentAtNode,
unstable_batchedUpdates,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');

let React;
let ReactDOM;
let ReactDOMClient;
let ReactDOMServer;

Expand Down Expand Up @@ -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(
<div>
<div />
</div>,
<div suppressHydrationWarning={true}>
<span />
</div>,
));

// @gate !disableLegacyMode
it('legacy mode can explicitly ignore reconnecting more children', () =>
expectMarkupMatch(
<div>
<div />
</div>,
<div suppressHydrationWarning={true}>
<div />
<div />
</div>,
));

// @gate !disableLegacyMode
it('legacy mode can explicitly ignore reconnecting fewer children', () =>
expectMarkupMatch(
<div>
<div />
<div />
</div>,
<div suppressHydrationWarning={true}>
<div />
</div>,
));

// @gate !disableLegacyMode
it('legacy mode can explicitly ignore reconnecting reordered children', () =>
expectMarkupMatch(
<div suppressHydrationWarning={true}>
<div />
<span />
</div>,
<div suppressHydrationWarning={true}>
<span />
<div />
</div>,
));
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div>
<Suspense fallback="Loading...">
<span ref={ref}>
<Child />
</span>
</Suspense>
</div>
);
}

// Don't suspend on the server.
suspend = false;
const finalHTML = ReactDOMServer.renderToString(<App />);

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(<App />, container);
});
}).toErrorDev(
'Warning: Cannot hydrate Suspense in legacy mode. Switch from ' +
'ReactDOM.hydrate(element, container) to ' +
'ReactDOMClient.hydrateRoot(container, <App />)' +
'.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(() => {});
Expand Down
Loading

0 comments on commit 670d61b

Please sign in to comment.