Skip to content

Use getComponentNameFromType for debug info for the key warning #27930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/react-client/src/__tests__/ReactFlight-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
5 changes: 1 addition & 4 deletions packages/react/src/ReactElementValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}>.`;
}
Expand Down
5 changes: 1 addition & 4 deletions packages/react/src/jsx/ReactJSXElementValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}>.`;
}
Expand Down
22 changes: 14 additions & 8 deletions packages/shared/getComponentNameFromType.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -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);
Expand Down