diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js
index 57c54a82b4b..45e6d6df5ad 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 7a1fa32cd86..779e1176270 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 8281057750e..44e9033fd35 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 da000079ee9..45d25e6ff11 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 817b96818fa..7fe6e49702a 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);