Skip to content

Commit

Permalink
Remove legacy hydration mode
Browse files Browse the repository at this point in the history
  • Loading branch information
sebmarkbage committed Mar 26, 2024
1 parent dbfbfb3 commit 24687d9
Show file tree
Hide file tree
Showing 20 changed files with 37 additions and 660 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 24687d9

Please sign in to comment.