diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 281ec1fea0582..9e2c5e41c062f 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -1355,6 +1355,19 @@ export function getFirstHydratableChildWithinSuspenseInstance( return getNextHydratable(parentInstance.nextSibling); } +export function validateHydratableInstance( + type: string, + props: Props, + hostContext: HostContext, +): boolean { + if (__DEV__) { + // TODO: take namespace into account when validating. + const hostContextDev: HostContextDev = (hostContext: any); + return validateDOMNesting(type, hostContextDev.ancestorInfo); + } + return true; +} + export function hydrateInstance( instance: Instance, type: string, @@ -1383,6 +1396,20 @@ export function hydrateInstance( ); } +export function validateHydratableTextInstance( + text: string, + hostContext: HostContext, +): boolean { + if (__DEV__) { + const hostContextDev = ((hostContext: any): HostContextDev); + const ancestor = hostContextDev.ancestorInfo.current; + if (ancestor != null) { + return validateTextNesting(text, ancestor.tag); + } + } + return true; +} + export function hydrateTextInstance( textInstance: TextInstance, text: string, diff --git a/packages/react-dom-bindings/src/client/validateDOMNesting.js b/packages/react-dom-bindings/src/client/validateDOMNesting.js index f910455f008be..ab49cb4f4b025 100644 --- a/packages/react-dom-bindings/src/client/validateDOMNesting.js +++ b/packages/react-dom-bindings/src/client/validateDOMNesting.js @@ -441,7 +441,7 @@ const didWarn: {[string]: boolean} = {}; function validateDOMNesting( childTag: string, ancestorInfo: AncestorInfoDev, -): void { +): boolean { if (__DEV__) { ancestorInfo = ancestorInfo || emptyAncestorInfoDev; const parentInfo = ancestorInfo.current; @@ -455,7 +455,7 @@ function validateDOMNesting( : findInvalidAncestorForTag(childTag, ancestorInfo); const invalidParentOrAncestor = invalidParent || invalidAncestor; if (!invalidParentOrAncestor) { - return; + return true; } const ancestorTag = invalidParentOrAncestor.tag; @@ -464,7 +464,7 @@ function validateDOMNesting( // eslint-disable-next-line react-internal/safe-string-coercion String(!!invalidParent) + '|' + childTag + '|' + ancestorTag; if (didWarn[warnKey]) { - return; + return false; } didWarn[warnKey] = true; @@ -477,45 +477,56 @@ function validateDOMNesting( 'the browser.'; } console.error( - '%s cannot appear as a child of <%s>.%s', + 'In HTML, %s cannot be a child of <%s>.%s\n' + + 'This will cause a hydration error.', tagDisplayName, ancestorTag, info, ); } else { console.error( - '%s cannot appear as a descendant of ' + '<%s>.', + 'In HTML, %s cannot be a descendant of <%s>.\n' + + 'This will cause a hydration error.', tagDisplayName, ancestorTag, ); } + return false; } + return true; } -function validateTextNesting(childText: string, parentTag: string): void { +function validateTextNesting(childText: string, parentTag: string): boolean { if (__DEV__) { if (isTagValidWithParent('#text', parentTag)) { - return; + return true; } // eslint-disable-next-line react-internal/safe-string-coercion const warnKey = '#text|' + parentTag; if (didWarn[warnKey]) { - return; + return false; } didWarn[warnKey] = true; if (/\S/.test(childText)) { - console.error('Text nodes cannot appear as a child of <%s>.', parentTag); + console.error( + 'In HTML, text nodes cannot be a child of <%s>.\n' + + 'This will cause a hydration error.', + parentTag, + ); } else { console.error( - 'Whitespace text nodes cannot appear as a child of <%s>. ' + + 'In HTML, whitespace text nodes cannot be a child of <%s>. ' + "Make sure you don't have any extra whitespace between tags on " + - 'each line of your source code.', + 'each line of your source code.\n' + + 'This will cause a hydration error.', parentTag, ); } + return false; } + return true; } export {updatedAncestorInfoDev, validateDOMNesting, validateTextNesting}; diff --git a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js index 53ad186761b74..6541293f51333 100644 --- a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js @@ -2188,8 +2188,9 @@ describe('ReactDOMComponent', () => { ); }); }).toErrorDev([ - 'Warning:
cannot appear as a descendant ' + - 'of
.' + + 'Warning: In HTML,
cannot be a descendant ' + + 'of
.\n' +
+ 'This will cause a hydration error.' +
// There is no outer `p` here because root container is not part of the stack.
'\n in p (at **)' +
'\n in span (at **)',
@@ -2241,22 +2243,25 @@ describe('ReactDOMComponent', () => {
root.render(