Skip to content

Commit 39d59ff

Browse files
committed
Add Owner Stack to attribute hydration mismatches
1 parent 9f42bba commit 39d59ff

File tree

2 files changed

+122
-25
lines changed

2 files changed

+122
-25
lines changed

packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js

+95-9
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ describe('ReactDOMServerHydration', () => {
171171
+ client
172172
- server
173173
174-
Owner Stack: <empty>",
174+
Owner Stack:
175+
in main (at **)
176+
in Mismatch (at **)",
175177
]
176178
`);
177179
}
@@ -236,7 +238,9 @@ describe('ReactDOMServerHydration', () => {
236238
+ This markup contains an nbsp entity:   client text
237239
- This markup contains an nbsp entity:   server text
238240
239-
Owner Stack: <empty>",
241+
Owner Stack:
242+
in div (at **)
243+
in Mismatch (at **)",
240244
]
241245
`);
242246
}
@@ -283,7 +287,9 @@ describe('ReactDOMServerHydration', () => {
283287
}}
284288
>
285289
286-
Owner Stack: <empty>",
290+
Owner Stack:
291+
in main (at **)
292+
in Mismatch (at **)",
287293
]
288294
`);
289295
});
@@ -325,7 +331,9 @@ describe('ReactDOMServerHydration', () => {
325331
- dir="rtl"
326332
>
327333
328-
Owner Stack: <empty>",
334+
Owner Stack:
335+
in main (at **)
336+
in Mismatch (at **)",
329337
]
330338
`);
331339
});
@@ -367,7 +375,9 @@ describe('ReactDOMServerHydration', () => {
367375
- dir={null}
368376
>
369377
370-
Owner Stack: <empty>",
378+
Owner Stack:
379+
in main (at **)
380+
in Mismatch (at **)",
371381
]
372382
`);
373383
});
@@ -409,7 +419,9 @@ describe('ReactDOMServerHydration', () => {
409419
- dir="rtl"
410420
>
411421
412-
Owner Stack: <empty>",
422+
Owner Stack:
423+
in main (at **)
424+
in Mismatch (at **)",
413425
]
414426
`);
415427
});
@@ -451,7 +463,9 @@ describe('ReactDOMServerHydration', () => {
451463
- dir="rtl"
452464
>
453465
454-
Owner Stack: <empty>",
466+
Owner Stack:
467+
in main (at **)
468+
in Mismatch (at **)",
455469
]
456470
`);
457471
});
@@ -492,7 +506,77 @@ describe('ReactDOMServerHydration', () => {
492506
- style={{opacity:"0"}}
493507
>
494508
495-
Owner Stack: <empty>",
509+
Owner Stack:
510+
in main (at **)
511+
in Mismatch (at **)",
512+
]
513+
`);
514+
});
515+
516+
// @gate __DEV__
517+
it('picks the DFS-first Fiber as the error Owner', () => {
518+
function LeftMismatch({isClient}) {
519+
return <div className={isClient ? 'client' : 'server'}></div>;
520+
}
521+
522+
function LeftIndirection({isClient}) {
523+
return <LeftMismatch isClient={isClient} />;
524+
}
525+
526+
function MiddleMismatch({isClient}) {
527+
return <span className={isClient ? 'client' : 'server'}></span>;
528+
}
529+
530+
function RightMisMatch({isClient}) {
531+
return <p className={isClient ? 'client' : 'server'}></p>;
532+
}
533+
534+
function App({isClient}) {
535+
return (
536+
<>
537+
<LeftIndirection isClient={isClient} />
538+
<MiddleMismatch isClient={isClient} />
539+
<RightMisMatch isClient={isClient} />
540+
</>
541+
);
542+
}
543+
expect(testMismatch(App)).toMatchInlineSnapshot(`
544+
[
545+
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:
546+
547+
- A server/client branch \`if (typeof window !== 'undefined')\`.
548+
- Variable input such as \`Date.now()\` or \`Math.random()\` which changes each time it's called.
549+
- Date formatting in a user's locale which doesn't match the server.
550+
- External changing data without sending a snapshot of it along with the HTML.
551+
- Invalid HTML tag nesting.
552+
553+
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
554+
555+
https://react.dev/link/hydration-mismatch
556+
557+
<App isClient={true}>
558+
<LeftIndirection isClient={true}>
559+
<LeftMismatch isClient={true}>
560+
<div
561+
+ className="client"
562+
- className="server"
563+
>
564+
<MiddleMismatch isClient={true}>
565+
<span
566+
+ className="client"
567+
- className="server"
568+
>
569+
<RightMisMatch isClient={true}>
570+
<p
571+
+ className="client"
572+
- className="server"
573+
>
574+
575+
Owner Stack:
576+
in div (at **)
577+
in LeftMismatch (at **)
578+
in LeftIndirection (at **)
579+
in App (at **)",
496580
]
497581
`);
498582
});
@@ -701,7 +785,9 @@ describe('ReactDOMServerHydration', () => {
701785
+ only
702786
-
703787
704-
Owner Stack: <empty>",
788+
Owner Stack:
789+
in div (at **)
790+
in Mismatch (at **)",
705791
]
706792
`);
707793
}

packages/react-reconciler/src/ReactFiberHydrationContext.js

+27-16
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import {
6767
import {queueRecoverableErrors} from './ReactFiberWorkLoop';
6868
import {getRootHostContainer, getHostContext} from './ReactFiberHostContext';
6969
import {describeDiff} from './ReactFiberHydrationDiffs';
70+
import {runWithFiberInDEV} from './ReactCurrentFiber';
7071

7172
// The deepest Fiber on the stack involved in a hydration context.
7273
// This may have been an insertion or a hydration.
@@ -749,22 +750,32 @@ export function emitPendingHydrationWarnings() {
749750
if (diffRoot !== null) {
750751
hydrationDiffRootDEV = null;
751752
const diff = describeDiff(diffRoot);
752-
console.error(
753-
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. " +
754-
'This can happen if a SSR-ed Client Component used:\n' +
755-
'\n' +
756-
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
757-
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
758-
"- Date formatting in a user's locale which doesn't match the server.\n" +
759-
'- External changing data without sending a snapshot of it along with the HTML.\n' +
760-
'- Invalid HTML tag nesting.\n' +
761-
'\n' +
762-
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n' +
763-
'\n' +
764-
'%s%s',
765-
'https://react.dev/link/hydration-mismatch',
766-
diff,
767-
);
753+
754+
// Just pick the DFS-first leaf as the owner.
755+
// Should be good enough since most warnings only have a single error.
756+
let diffOwner: HydrationDiffNode = diffRoot;
757+
while (diffOwner.children.length > 0) {
758+
diffOwner = diffOwner.children[0];
759+
}
760+
761+
runWithFiberInDEV(diffOwner.fiber, () => {
762+
console.error(
763+
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. " +
764+
'This can happen if a SSR-ed Client Component used:\n' +
765+
'\n' +
766+
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
767+
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
768+
"- Date formatting in a user's locale which doesn't match the server.\n" +
769+
'- External changing data without sending a snapshot of it along with the HTML.\n' +
770+
'- Invalid HTML tag nesting.\n' +
771+
'\n' +
772+
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n' +
773+
'\n' +
774+
'%s%s',
775+
'https://react.dev/link/hydration-mismatch',
776+
diff,
777+
);
778+
});
768779
}
769780
}
770781
}

0 commit comments

Comments
 (0)