diff --git a/packages/react/src/ReactChildren.js b/packages/react/src/ReactChildren.js
index 391c0985ef85e..7296a452c00ad 100644
--- a/packages/react/src/ReactChildren.js
+++ b/packages/react/src/ReactChildren.js
@@ -229,11 +229,21 @@ function mapIntoArray(
childKey,
);
if (__DEV__) {
- if (nameSoFar !== '' && mappedChild.key == null) {
- // We need to validate that this child should have had a key before assigning it one.
- if (!newChild._store.validated) {
- // We mark this child as having failed validation but we let the actual renderer
- // print the warning later.
+ // If `child` was an element without a `key`, we need to validate if
+ // it should have had a `key`, before assigning one to `mappedChild`.
+ // $FlowFixMe[incompatible-type] Flow incorrectly thinks React.Portal doesn't have a key
+ if (
+ nameSoFar !== '' &&
+ child != null &&
+ isValidElement(child) &&
+ child.key == null
+ ) {
+ // We check truthiness of `child._store.validated` instead of being
+ // inequal to `1` to provide a bit of backward compatibility for any
+ // libraries (like `fbt`) which may be hacking this property.
+ if (child._store && !child._store.validated) {
+ // Mark this child as having failed validation, but let the actual
+ // renderer print the warning later.
newChild._store.validated = 2;
}
}
diff --git a/packages/react/src/__tests__/ReactChildren-test.js b/packages/react/src/__tests__/ReactChildren-test.js
index 6a97465d3d97f..c4e92c44cccb0 100644
--- a/packages/react/src/__tests__/ReactChildren-test.js
+++ b/packages/react/src/__tests__/ReactChildren-test.js
@@ -868,7 +868,105 @@ describe('ReactChildren', () => {
]);
});
- it('should warn for flattened children lists', async () => {
+ it('warns for mapped list children without keys', async () => {
+ function ComponentRenderingMappedChildren({children}) {
+ return (
+
+ {React.Children.map(children, child => (
+
+ ))}
+
+ );
+ }
+
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(() => {
+ root.render(
+
+ {[]}
+ ,
+ );
+ });
+ }).toErrorDev([
+ 'Warning: Each child in a list should have a unique "key" prop.',
+ ]);
+ });
+
+ it('does not warn for mapped static children without keys', async () => {
+ function ComponentRenderingMappedChildren({children}) {
+ return (
+
+ {React.Children.map(children, child => (
+
+ ))}
+
+ );
+ }
+
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(() => {
+ root.render(
+
+
+
+ ,
+ );
+ });
+ }).toErrorDev([]);
+ });
+
+ it('warns for cloned list children without keys', async () => {
+ function ComponentRenderingClonedChildren({children}) {
+ return (
+
+ {React.Children.map(children, child => React.cloneElement(child))}
+
+ );
+ }
+
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(() => {
+ root.render(
+
+ {[]}
+ ,
+ );
+ });
+ }).toErrorDev([
+ 'Warning: Each child in a list should have a unique "key" prop.',
+ ]);
+ });
+
+ it('does not warn for cloned static children without keys', async () => {
+ function ComponentRenderingClonedChildren({children}) {
+ return (
+
+ {React.Children.map(children, child => React.cloneElement(child))}
+
+ );
+ }
+
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(() => {
+ root.render(
+
+
+
+ ,
+ );
+ });
+ }).toErrorDev([]);
+ });
+
+ it('warns for flattened list children without keys', async () => {
function ComponentRenderingFlattenedChildren({children}) {
return {React.Children.toArray(children)}
;
}
@@ -888,7 +986,7 @@ describe('ReactChildren', () => {
]);
});
- it('does not warn for flattened positional children', async () => {
+ it('does not warn for flattened static children without keys', async () => {
function ComponentRenderingFlattenedChildren({children}) {
return {React.Children.toArray(children)}
;
}