From c36c5265ff2889383b1dce4d7ea92ff9bc47530a Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 11 Jan 2024 16:58:59 -0500 Subject: [PATCH] Use getComponentNameFromType for debug info for the key warning If this is a client reference we shouldn't dot into it. --- .../src/__tests__/ReactFlight-test.js | 16 +++++++++++++ .../__tests__/ReactFlightDOMBrowser-test.js | 23 +++++++++++++++++++ packages/react/src/ReactElementValidator.js | 5 +--- .../react/src/jsx/ReactJSXElementValidator.js | 5 +--- packages/shared/getComponentNameFromType.js | 22 +++++++++++------- 5 files changed, 55 insertions(+), 16 deletions(-) diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index 57c54a82b4b91..45e6d6df5ad4f 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -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( + {Array(6).fill(
no key
)}
, + ); + 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() {} diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js index 7a1fa32cd863d..779e117627072 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -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( + <> + {Array(6).fill(
no key
)}
+ + {Array(6).fill(
no key
)} +
+ , + 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 ( diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js index 8281057750e9c..44e9033fd3578 100644 --- a/packages/react/src/ReactElementValidator.js +++ b/packages/react/src/ReactElementValidator.js @@ -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}>.`; } diff --git a/packages/react/src/jsx/ReactJSXElementValidator.js b/packages/react/src/jsx/ReactJSXElementValidator.js index da000079ee90f..45d25e6ff11aa 100644 --- a/packages/react/src/jsx/ReactJSXElementValidator.js +++ b/packages/react/src/jsx/ReactJSXElementValidator.js @@ -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}>.`; } diff --git a/packages/shared/getComponentNameFromType.js b/packages/shared/getComponentNameFromType.js index 817b96818fa63..7fe6e49702ad9 100644 --- a/packages/shared/getComponentNameFromType.js +++ b/packages/shared/getComponentNameFromType.js @@ -52,21 +52,19 @@ function getContextName(type: ReactContext) { 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 = (type: any);