Skip to content

Commit

Permalink
Back out #28476 and #28458
Browse files Browse the repository at this point in the history
Back out "Move Hydration Warnings from the DOM Config into the Fiber reconciliation (#28476)"

Original commit changeset: 4b8dfd6

Back out "Remove enableClientRenderFallbackOnTextMismatch flag (#28458)"

Original commit changeset: 84c84d7
  • Loading branch information
kassens committed Mar 26, 2024
1 parent 4b8dfd6 commit 0f3b777
Show file tree
Hide file tree
Showing 27 changed files with 859 additions and 660 deletions.
381 changes: 159 additions & 222 deletions packages/react-dom-bindings/src/client/ReactDOMComponent.js

Large diffs are not rendered by default.

239 changes: 187 additions & 52 deletions packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ import {hasRole} from './DOMAccessibilityRoles';
import {
setInitialProperties,
updateProperties,
hydrateProperties,
hydrateText,
diffHydratedProperties,
getPropsFromElement,
diffHydratedText,
trapClickOnNonInteractiveElement,
checkForUnmatchedText,
warnForDeletedHydratableElement,
warnForDeletedHydratableText,
warnForInsertedHydratedElement,
warnForInsertedHydratedText,
} from './ReactDOMComponent';
import {getSelectionInformation, restoreSelection} from './ReactInputSelection';
import setTextContent from './setTextContent';
Expand Down Expand Up @@ -1340,26 +1342,6 @@ export function getFirstHydratableChildWithinSuspenseInstance(
return getNextHydratable(parentInstance.nextSibling);
}

export function describeHydratableInstanceForDevWarnings(
instance: HydratableInstance,
): string | {type: string, props: $ReadOnly<Props>} {
// Reverse engineer a pseudo react-element from hydratable instnace
if (instance.nodeType === ELEMENT_NODE) {
// Reverse engineer a set of props that can print for dev warnings
return {
type: instance.nodeName.toLowerCase(),
props: getPropsFromElement((instance: any)),
};
} else if (instance.nodeType === COMMENT_NODE) {
return {
type: 'Suspense',
props: {},
};
} else {
return instance.nodeValue;
}
}

export function validateHydratableInstance(
type: string,
props: Props,
Expand All @@ -1379,23 +1361,14 @@ export function hydrateInstance(
props: Props,
hostContext: HostContext,
internalInstanceHandle: Object,
): boolean {
shouldWarnDev: boolean,
): void {
precacheFiberNode(internalInstanceHandle, instance);
// TODO: Possibly defer this until the commit phase where all the events
// get attached.
updateFiberProps(instance, props);

return hydrateProperties(instance, type, props, hostContext);
}

// Returns a Map of properties that were different on the server.
export function diffHydratedPropsForDevWarnings(
instance: Instance,
type: string,
props: Props,
hostContext: HostContext,
): null | $ReadOnly<Props> {
return diffHydratedProperties(instance, type, props, hostContext);
diffHydratedProperties(instance, type, props, shouldWarnDev, hostContext);
}

export function validateHydratableTextInstance(
Expand All @@ -1416,26 +1389,11 @@ export function hydrateTextInstance(
textInstance: TextInstance,
text: string,
internalInstanceHandle: Object,
parentInstanceProps: null | Props,
shouldWarnDev: boolean,
): boolean {
precacheFiberNode(internalInstanceHandle, textInstance);

return hydrateText(textInstance, text, parentInstanceProps);
}

// Returns the server text if it differs from the client.
export function diffHydratedTextForDevWarnings(
textInstance: TextInstance,
text: string,
parentProps: null | Props,
): null | string {
if (
parentProps === null ||
parentProps[SUPPRESS_HYDRATION_WARNING] !== true
) {
return diffHydratedText(textInstance, text);
}
return null;
return diffHydratedText(textInstance, text);
}

export function hydrateSuspenseInstance(
Expand Down Expand Up @@ -1527,6 +1485,183 @@ export function shouldDeleteUnhydratedTailInstances(
return parentType !== 'form' && parentType !== 'button';
}

export function didNotMatchHydratedContainerTextInstance(
parentContainer: Container,
textInstance: TextInstance,
text: string,
shouldWarnDev: boolean,
) {
checkForUnmatchedText(textInstance.nodeValue, text, shouldWarnDev);
}

export function didNotMatchHydratedTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
textInstance: TextInstance,
text: string,
shouldWarnDev: boolean,
) {
if (parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
checkForUnmatchedText(textInstance.nodeValue, text, shouldWarnDev);
}
}

export function didNotHydrateInstanceWithinContainer(
parentContainer: Container,
instance: HydratableInstance,
) {
if (__DEV__) {
if (instance.nodeType === ELEMENT_NODE) {
warnForDeletedHydratableElement(parentContainer, (instance: any));
} else if (instance.nodeType === COMMENT_NODE) {
// TODO: warnForDeletedHydratableSuspenseBoundary
} else {
warnForDeletedHydratableText(parentContainer, (instance: any));
}
}
}

export function didNotHydrateInstanceWithinSuspenseInstance(
parentInstance: SuspenseInstance,
instance: HydratableInstance,
) {
if (__DEV__) {
// $FlowFixMe[incompatible-type]: Only Element or Document can be parent nodes.
const parentNode: Element | Document | null = parentInstance.parentNode;
if (parentNode !== null) {
if (instance.nodeType === ELEMENT_NODE) {
warnForDeletedHydratableElement(parentNode, (instance: any));
} else if (instance.nodeType === COMMENT_NODE) {
// TODO: warnForDeletedHydratableSuspenseBoundary
} else {
warnForDeletedHydratableText(parentNode, (instance: any));
}
}
}
}

export function didNotHydrateInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
instance: HydratableInstance,
) {
if (__DEV__) {
if (instance.nodeType === ELEMENT_NODE) {
warnForDeletedHydratableElement(parentInstance, (instance: any));
} else if (instance.nodeType === COMMENT_NODE) {
// TODO: warnForDeletedHydratableSuspenseBoundary
} else {
warnForDeletedHydratableText(parentInstance, (instance: any));
}
}
}

export function didNotFindHydratableInstanceWithinContainer(
parentContainer: Container,
type: string,
props: Props,
) {
if (__DEV__) {
warnForInsertedHydratedElement(parentContainer, type, props);
}
}

export function didNotFindHydratableTextInstanceWithinContainer(
parentContainer: Container,
text: string,
) {
if (__DEV__) {
warnForInsertedHydratedText(parentContainer, text);
}
}

export function didNotFindHydratableSuspenseInstanceWithinContainer(
parentContainer: Container,
) {
if (__DEV__) {
// TODO: warnForInsertedHydratedSuspense(parentContainer);
}
}

export function didNotFindHydratableInstanceWithinSuspenseInstance(
parentInstance: SuspenseInstance,
type: string,
props: Props,
) {
if (__DEV__) {
// $FlowFixMe[incompatible-type]: Only Element or Document can be parent nodes.
const parentNode: Element | Document | null = parentInstance.parentNode;
if (parentNode !== null)
warnForInsertedHydratedElement(parentNode, type, props);
}
}

export function didNotFindHydratableTextInstanceWithinSuspenseInstance(
parentInstance: SuspenseInstance,
text: string,
) {
if (__DEV__) {
// $FlowFixMe[incompatible-type]: Only Element or Document can be parent nodes.
const parentNode: Element | Document | null = parentInstance.parentNode;
if (parentNode !== null) warnForInsertedHydratedText(parentNode, text);
}
}

export function didNotFindHydratableSuspenseInstanceWithinSuspenseInstance(
parentInstance: SuspenseInstance,
) {
if (__DEV__) {
// const parentNode: Element | Document | null = parentInstance.parentNode;
// TODO: warnForInsertedHydratedSuspense(parentNode);
}
}

export function didNotFindHydratableInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
type: string,
props: Props,
) {
if (__DEV__) {
warnForInsertedHydratedElement(parentInstance, type, props);
}
}

export function didNotFindHydratableTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
text: string,
) {
if (__DEV__) {
warnForInsertedHydratedText(parentInstance, text);
}
}

export function didNotFindHydratableSuspenseInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
) {
if (__DEV__) {
// TODO: warnForInsertedHydratedSuspense(parentInstance);
}
}

export function errorHydratingContainer(parentContainer: Container): void {
if (__DEV__) {
// TODO: This gets logged by onRecoverableError, too, so we should be
// able to remove it.
console.error(
'An error occurred during hydration. The server HTML was replaced with client content in <%s>.',
parentContainer.nodeName.toLowerCase(),
);
}
}

// -------------------
// Test Selectors
// -------------------
Expand Down
10 changes: 6 additions & 4 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2407,8 +2407,8 @@ describe('ReactDOMFizzServer', () => {
]);
}).toErrorDev(
[
'Warning: An error occurred during hydration. The server HTML was replaced with client content.',
'Warning: Expected server HTML to contain a matching <div> in the root.\n' +
'Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.',
'Warning: Expected server HTML to contain a matching <div> in <div>.\n' +
' in div (at **)\n' +
' in App (at **)',
],
Expand Down Expand Up @@ -2492,7 +2492,7 @@ describe('ReactDOMFizzServer', () => {
}).toErrorDev(
[
'Warning: An error occurred during hydration. The server HTML was replaced with client content',
'Warning: Expected server HTML to contain a matching <div> in the root.\n' +
'Warning: Expected server HTML to contain a matching <div> in <div>.\n' +
' in div (at **)\n' +
' in App (at **)',
],
Expand Down Expand Up @@ -4419,6 +4419,7 @@ describe('ReactDOMFizzServer', () => {
);
});

// @gate enableClientRenderFallbackOnTextMismatch
it('#24384: Suspending should halt hydration warnings but still emit hydration warnings after unsuspending if mismatches are genuine', async () => {
const makeApp = () => {
let resolve, resolved;
Expand Down Expand Up @@ -4504,6 +4505,7 @@ describe('ReactDOMFizzServer', () => {
await waitForAll([]);
});

// @gate enableClientRenderFallbackOnTextMismatch
it('only warns once on hydration mismatch while within a suspense boundary', async () => {
const originalConsoleError = console.error;
const mockError = jest.fn();
Expand Down Expand Up @@ -6343,7 +6345,7 @@ describe('ReactDOMFizzServer', () => {
await waitForAll([]);
}).toErrorDev(
[
'Expected server HTML to contain a matching <span> in the root',
'Expected server HTML to contain a matching <span> in <div>',
'An error occurred during hydration',
],
{withoutStack: 1},
Expand Down
Loading

0 comments on commit 0f3b777

Please sign in to comment.