@samitouri / QOS-React-1 / commits / 0ac3ea471f

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);