Repository: facebook/react
Pull request: Use getComponentNameFromType for debug info for the key warning

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.


Changed files:
filename: packages/react-client/src/__tests__/ReactFlight-test.js
patch: @@ -1009,6 +1009,22 @@ describe('ReactFlight', () => {
     ReactNoopFlightClient.read(transport);
   });
 
+  it('should warn in DEV a child is missing keys', () => {
+    function ParentClient({children}) {
+      return children;
+    }
+    const Parent = clientReference(ParentClient);
+    expect(() => {
+      const transport = ReactNoopFlightServer.render(
+        <Parent>{Array(6).fill(<div>no key</div>)}</Parent>,
+      );
+      ReactNoopFlightClient.read(transport);
+    }).toErrorDev(
+      'Each child in a list should have a unique "key" prop. ' +
+        'See https://reactjs.org/link/warning-keys for more information.',
+    );
+  });
+
   it('should error if a class instance is passed to a host component', () => {
     class Foo {
       method() {}

filename: packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
patch: @@ -590,6 +590,29 @@ describe('ReactFlightDOMBrowser', () => {
     expect(reportedErrors).toEqual(['for reasons']);
   });
 
+  it('should warn in DEV a child is missing keys', async () => {
+    function ParentClient({children}) {
+      return children;
+    }
+    const Parent = clientExports(ParentClient);
+    const ParentModule = clientExports({Parent: ParentClient});
+    await expect(async () => {
+      const stream = ReactServerDOMServer.renderToReadableStream(
+        <>
+          <Parent>{Array(6).fill(<div>no key</div>)}</Parent>
+          <ParentModule.Parent>
+            {Array(6).fill(<div>no key</div>)}
+          </ParentModule.Parent>
+        </>,
+        webpackMap,
+      );
+      await ReactServerDOMClient.createFromReadableStream(stream);
+    }).toErrorDev(
+      'Each child in a list should have a unique "key" prop. ' +
+        'See https://reactjs.org/link/warning-keys for more information.',
+    );
+  });
+
   it('basic use(promise)', async () => {
     function Server() {
       return (

filename: packages/react/src/ReactElementValidator.js
patch: @@ -96,10 +96,7 @@ function getCurrentComponentErrorInfo(parentType) {
   let info = getDeclarationErrorAddendum();
 
   if (!info) {
-    const parentName =
-      typeof parentType === 'string'
-        ? parentType
-        : parentType.displayName || parentType.name;
+    const parentName = getComponentNameFromType(parentType);
     if (parentName) {
       info = `\n\nCheck the top-level render call using <${parentName}>.`;
     }

filename: packages/react/src/jsx/ReactJSXElementValidator.js
patch: @@ -108,10 +108,7 @@ function getCurrentComponentErrorInfo(parentType) {
     let info = getDeclarationErrorAddendum();
 
     if (!info) {
-      const parentName =
-        typeof parentType === 'string'
-          ? parentType
-          : parentType.displayName || parentType.name;
+      const parentName = getComponentNameFromType(parentType);
       if (parentName) {
         info = `\n\nCheck the top-level render call using <${parentName}>.`;
       }

filename: packages/shared/getComponentNameFromType.js
patch: @@ -52,21 +52,19 @@ function getContextName(type: ReactContext<any>) {
   return type.displayName || 'Context';
 }
 
+const REACT_CLIENT_REFERENCE = Symbol.for('react.client.reference');
+
 // Note that the reconciler package should generally prefer to use getComponentNameFromFiber() instead.
 export default function getComponentNameFromType(type: mixed): string | null {
   if (type == null) {
     // Host root, text node or just invalid type.
     return null;
   }
-  if (__DEV__) {
-    if (typeof (type: any).tag === 'number') {
-      console.error(
-        'Received an unexpected object in getComponentNameFromType(). ' +
-          'This is likely a bug in React. Please file an issue.',
-      );
-    }
-  }
   if (typeof type === 'function') {
+    if ((type: any).$$typeof === REACT_CLIENT_REFERENCE) {
+      // TODO: Create a convention for naming client references with debug info.
+      return null;
+    }
     return (type: any).displayName || type.name || null;
   }
   if (typeof type === 'string') {
@@ -96,6 +94,14 @@ export default function getComponentNameFromType(type: mixed): string | null {
       }
   }
   if (typeof type === 'object') {
+    if (__DEV__) {
+      if (typeof (type: any).tag === 'number') {
+        console.error(
+          'Received an unexpected object in getComponentNameFromType(). ' +
+            'This is likely a bug in React. Please file an issue.',
+        );
+      }
+    }
     switch (type.$$typeof) {
       case REACT_CONTEXT_TYPE:
         const context: ReactContext<any> = (type: any);