Use getComponentNameFromType for debug info for the key warning (#27930)
If this is a client reference we shouldn't dot into it, which would throw in the proxy. Interestingly our client references don't really have a `name` associated with them for debug information so a component type doesn't show up in error logs even though it seems like it should.
Sebastian Markbåge committed
Jan 11, 2024 at 17:24 UTC
0ac3ea471fbcb7d79bc7d36179e960c72c779e76
5 files changed
+55
-16
packages/react-client/src/__tests__/ReactFlight-test.js
+16
@@ -1009,6 +1009,22 @@ describe('ReactFlight', () => {
1009
ReactNoopFlightClient.read(transport);
1010
});
1011
1012
+ it('should warn in DEV a child is missing keys', () => {
1013
+ function ParentClient({children}) {
1014
+ return children;
1015
+ }
1016
+ const Parent = clientReference(ParentClient);
1017
+ expect(() => {
1018
+ const transport = ReactNoopFlightServer.render(
1019
+ <Parent>{Array(6).fill(<div>no key</div>)}</Parent>,
1020
+ );
1021
+ ReactNoopFlightClient.read(transport);
1022
+ }).toErrorDev(
1023
+ 'Each child in a list should have a unique "key" prop. ' +
1024
+ 'See https://reactjs.org/link/warning-keys for more information.',
1025
+ );
1026
+ });
1027
+
1028
it('should error if a class instance is passed to a host component', () => {
1029
class Foo {
1030
method() {}
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
+23
@@ -590,6 +590,29 @@ describe('ReactFlightDOMBrowser', () => {
590
expect(reportedErrors).toEqual(['for reasons']);
591
});
592
593
+ it('should warn in DEV a child is missing keys', async () => {
594
+ function ParentClient({children}) {
595
+ return children;
596
+ }
597
+ const Parent = clientExports(ParentClient);
598
+ const ParentModule = clientExports({Parent: ParentClient});
599
+ await expect(async () => {
600
+ const stream = ReactServerDOMServer.renderToReadableStream(
601
+ <>
602
+ <Parent>{Array(6).fill(<div>no key</div>)}</Parent>
603
+ <ParentModule.Parent>
604
+ {Array(6).fill(<div>no key</div>)}
605
+ </ParentModule.Parent>
606
+ </>,
607
+ webpackMap,
608
+ );
609
+ await ReactServerDOMClient.createFromReadableStream(stream);
610
+ }).toErrorDev(
611
+ 'Each child in a list should have a unique "key" prop. ' +
612
+ 'See https://reactjs.org/link/warning-keys for more information.',
613
+ );
614
+ });
615
+
616
it('basic use(promise)', async () => {
617
function Server() {
618
return (
packages/react/src/ReactElementValidator.js
+1
-4
@@ -96,10 +96,7 @@ function getCurrentComponentErrorInfo(parentType) {
96
let info = getDeclarationErrorAddendum();
97
98
if (!info) {
99
- const parentName =
100
- typeof parentType === 'string'
101
- ? parentType
102
- : parentType.displayName || parentType.name;
99
+ const parentName = getComponentNameFromType(parentType);
100
if (parentName) {
101
info = `\n\nCheck the top-level render call using <${parentName}>.`;
102
}
packages/react/src/jsx/ReactJSXElementValidator.js
+1
-4
@@ -108,10 +108,7 @@ function getCurrentComponentErrorInfo(parentType) {
108
let info = getDeclarationErrorAddendum();
109
110
if (!info) {
111
- const parentName =
112
- typeof parentType === 'string'
113
- ? parentType
114
- : parentType.displayName || parentType.name;
111
+ const parentName = getComponentNameFromType(parentType);
112
if (parentName) {
113
info = `\n\nCheck the top-level render call using <${parentName}>.`;
114
}
packages/shared/getComponentNameFromType.js
+14
-8
@@ -52,21 +52,19 @@ function getContextName(type: ReactContext<any>) {
52
return type.displayName || 'Context';
53
}
54
55
+const REACT_CLIENT_REFERENCE = Symbol.for('react.client.reference');
56
+
57
// Note that the reconciler package should generally prefer to use getComponentNameFromFiber() instead.
58
export default function getComponentNameFromType(type: mixed): string | null {
59
if (type == null) {
60
// Host root, text node or just invalid type.
61
return null;
62
}
61
- if (__DEV__) {
62
- if (typeof (type: any).tag === 'number') {
63
- console.error(
64
- 'Received an unexpected object in getComponentNameFromType(). ' +
65
- 'This is likely a bug in React. Please file an issue.',
66
- );
67
- }
68
- }
63
if (typeof type === 'function') {
64
+ if ((type: any).$$typeof === REACT_CLIENT_REFERENCE) {
65
+ // TODO: Create a convention for naming client references with debug info.
66
+ return null;
67
+ }
68
return (type: any).displayName || type.name || null;
69
}
70
if (typeof type === 'string') {
@@ -96,6 +94,14 @@ export default function getComponentNameFromType(type: mixed): string | null {
94
}
95
}
96
if (typeof type === 'object') {
97
+ if (__DEV__) {
98
+ if (typeof (type: any).tag === 'number') {
99
+ console.error(
100
+ 'Received an unexpected object in getComponentNameFromType(). ' +
101
+ 'This is likely a bug in React. Please file an issue.',
102
+ );
103
+ }
104
+ }
105
switch (type.$$typeof) {
106
case REACT_CONTEXT_TYPE:
107
const context: ReactContext<any> = (type: any);