Skip to content

Commit 907484a

Browse files
committed
Add Owner Stack to attribute hydration mismatches
1 parent 9f42bba commit 907484a

8 files changed

+276
-187
lines changed

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

+51-53
Original file line numberDiff line numberDiff line change
@@ -201,25 +201,24 @@ describe('ReactDOMFizzForm', () => {
201201
await act(async () => {
202202
ReactDOMClient.hydrateRoot(container, <App isClient={true} />);
203203
});
204-
assertConsoleErrorDev(
205-
[
206-
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
207-
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n" +
208-
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
209-
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
210-
"- Date formatting in a user's locale which doesn't match the server.\n" +
211-
'- External changing data without sending a snapshot of it along with the HTML.\n' +
212-
'- Invalid HTML tag nesting.\n\n' +
213-
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
214-
'https://react.dev/link/hydration-mismatch\n\n' +
215-
' <App isClient={true}>\n' +
216-
' <form\n' +
217-
'+ action="action"\n' +
218-
'- action="function"\n' +
219-
' >\n',
220-
],
221-
{withoutStack: true},
222-
);
204+
assertConsoleErrorDev([
205+
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
206+
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n" +
207+
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
208+
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
209+
"- Date formatting in a user's locale which doesn't match the server.\n" +
210+
'- External changing data without sending a snapshot of it along with the HTML.\n' +
211+
'- Invalid HTML tag nesting.\n\n' +
212+
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
213+
'https://react.dev/link/hydration-mismatch\n\n' +
214+
' <App isClient={true}>\n' +
215+
' <form\n' +
216+
'+ action="action"\n' +
217+
'- action="function"\n' +
218+
' >\n' +
219+
'\n in form (at **)' +
220+
'\n in App (at **)',
221+
]);
223222
});
224223

225224
it('should ideally warn when passing a string during SSR and function during hydration', async () => {
@@ -392,40 +391,39 @@ describe('ReactDOMFizzForm', () => {
392391
await act(async () => {
393392
root = ReactDOMClient.hydrateRoot(container, <App />);
394393
});
395-
assertConsoleErrorDev(
396-
[
397-
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
398-
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n" +
399-
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
400-
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
401-
"- Date formatting in a user's locale which doesn't match the server.\n" +
402-
'- External changing data without sending a snapshot of it along with the HTML.\n' +
403-
'- Invalid HTML tag nesting.\n\n' +
404-
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
405-
'https://react.dev/link/hydration-mismatch\n\n' +
406-
' <App>\n' +
407-
' <form\n' +
408-
' action={function action}\n' +
409-
' ref={{current:null}}\n' +
410-
'+ method="DELETE"\n' +
411-
'- method={null}\n' +
412-
' >\n' +
413-
' <input\n' +
414-
' type="submit"\n' +
415-
' formAction={function action}\n' +
416-
' ref={{current:null}}\n' +
417-
'+ formTarget="elsewhere"\n' +
418-
'- formTarget={null}\n' +
419-
' >\n' +
420-
' <button\n' +
421-
' formAction={function action}\n' +
422-
' ref={{current:null}}\n' +
423-
'+ formEncType="text/plain"\n' +
424-
'- formEncType={null}\n' +
425-
' >\n',
426-
],
427-
{withoutStack: true},
428-
);
394+
assertConsoleErrorDev([
395+
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
396+
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n" +
397+
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
398+
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
399+
"- Date formatting in a user's locale which doesn't match the server.\n" +
400+
'- External changing data without sending a snapshot of it along with the HTML.\n' +
401+
'- Invalid HTML tag nesting.\n\n' +
402+
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
403+
'https://react.dev/link/hydration-mismatch\n\n' +
404+
' <App>\n' +
405+
' <form\n' +
406+
' action={function action}\n' +
407+
' ref={{current:null}}\n' +
408+
'+ method="DELETE"\n' +
409+
'- method={null}\n' +
410+
' >\n' +
411+
' <input\n' +
412+
' type="submit"\n' +
413+
' formAction={function action}\n' +
414+
' ref={{current:null}}\n' +
415+
'+ formTarget="elsewhere"\n' +
416+
'- formTarget={null}\n' +
417+
' >\n' +
418+
' <button\n' +
419+
' formAction={function action}\n' +
420+
' ref={{current:null}}\n' +
421+
'+ formEncType="text/plain"\n' +
422+
'- formEncType={null}\n' +
423+
' >\n' +
424+
'\n in input (at **)' +
425+
'\n in App (at **)',
426+
]);
429427
await act(async () => {
430428
root.render(<App isUpdate={true} />);
431429
});

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -10233,7 +10233,7 @@ describe('ReactDOMFizzServer', () => {
1023310233
'\n+ client' +
1023410234
'\n- server' +
1023510235
'\n' +
10236-
'\n in Suspense (at **)' +
10236+
'\n in meta (at **)' +
1023710237
'\n in ClientApp (at **)',
1023810238
]);
1023910239
}

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'} />;
520+
}
521+
522+
function LeftIndirection({isClient}) {
523+
return <LeftMismatch isClient={isClient} />;
524+
}
525+
526+
function MiddleMismatch({isClient}) {
527+
return <span className={isClient ? 'client' : 'server'} />;
528+
}
529+
530+
function RightMisMatch({isClient}) {
531+
return <p className={isClient ? 'client' : 'server'} />;
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-dom/src/__tests__/ReactDOMRoot-test.js

+20-22
Original file line numberDiff line numberDiff line change
@@ -164,28 +164,26 @@ describe('ReactDOMRoot', () => {
164164
</div>,
165165
);
166166
await waitForAll([]);
167-
assertConsoleErrorDev(
168-
[
169-
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
170-
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n" +
171-
'\n' +
172-
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
173-
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
174-
"- Date formatting in a user's locale which doesn't match the server.\n" +
175-
'- External changing data without sending a snapshot of it along with the HTML.\n' +
176-
'- Invalid HTML tag nesting.\n' +
177-
'\n' +
178-
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n' +
179-
'\n' +
180-
'https://react.dev/link/hydration-mismatch\n' +
181-
'\n' +
182-
' <div>\n' +
183-
' <span\n' +
184-
'- className="extra"\n' +
185-
' >\n',
186-
],
187-
{withoutStack: true},
188-
);
167+
assertConsoleErrorDev([
168+
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
169+
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n" +
170+
'\n' +
171+
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
172+
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
173+
"- Date formatting in a user's locale which doesn't match the server.\n" +
174+
'- External changing data without sending a snapshot of it along with the HTML.\n' +
175+
'- Invalid HTML tag nesting.\n' +
176+
'\n' +
177+
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n' +
178+
'\n' +
179+
'https://react.dev/link/hydration-mismatch\n' +
180+
'\n' +
181+
' <div>\n' +
182+
' <span\n' +
183+
'- className="extra"\n' +
184+
' >\n' +
185+
'\n in span (at **)',
186+
]);
189187
});
190188

191189
it('clears existing children', async () => {

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

+24-26
Original file line numberDiff line numberDiff line change
@@ -547,32 +547,30 @@ describe('ReactDOM HostSingleton', () => {
547547
);
548548
expect(hydrationErrors).toEqual([]);
549549
await waitForAll([]);
550-
assertConsoleErrorDev(
551-
[
552-
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
553-
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n" +
554-
'\n' +
555-
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
556-
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
557-
"- Date formatting in a user's locale which doesn't match the server.\n" +
558-
'- External changing data without sending a snapshot of it along with the HTML.\n' +
559-
'- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension installed ' +
560-
'which messes with the HTML before React loaded.\n' +
561-
'\n' +
562-
'https://react.dev/link/hydration-mismatch\n' +
563-
'\n' +
564-
' <html\n' +
565-
'+ data-client-foo="foo"\n' +
566-
'- data-client-foo={null}\n' +
567-
' >\n' +
568-
' <head>\n' +
569-
' <body\n' +
570-
'+ data-client-baz="baz"\n' +
571-
'- data-client-baz={null}\n' +
572-
' >\n',
573-
],
574-
{withoutStack: true},
575-
);
550+
assertConsoleErrorDev([
551+
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
552+
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n" +
553+
'\n' +
554+
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
555+
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
556+
"- Date formatting in a user's locale which doesn't match the server.\n" +
557+
'- External changing data without sending a snapshot of it along with the HTML.\n' +
558+
'- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension installed ' +
559+
'which messes with the HTML before React loaded.\n' +
560+
'\n' +
561+
'https://react.dev/link/hydration-mismatch\n' +
562+
'\n' +
563+
' <html\n' +
564+
'+ data-client-foo="foo"\n' +
565+
'- data-client-foo={null}\n' +
566+
' >\n' +
567+
' <head>\n' +
568+
' <body\n' +
569+
'+ data-client-baz="baz"\n' +
570+
'- data-client-baz={null}\n' +
571+
' >\n' +
572+
'\n in body (at **)',
573+
]);
576574
expect(persistentElements).toEqual([
577575
document.documentElement,
578576
document.head,

0 commit comments

Comments
 (0)