Fix `key` Warning for Flattened Positional Children (#29662)
## Summary https://github.com/facebook/react/pull/29088 introduced a regression triggering this warning when rendering flattened positional children: > Each child in a list should have a unique "key" prop. The specific scenario that triggers this is when rendering multiple positional children (which do not require unique `key` props) after flattening them with one of the `React.Children` utilities (e.g. `React.Children.toArray`). The refactored logic in `React.Children` incorrectly drops the `element._store.validated` property in `__DEV__`. This diff fixes the bug and introduces a unit test to prevent future regressions. ## How did you test this change? ``` $ yarn test ReactChildren-test.js ```
Timothy Yung committed
May 30, 2024 at 07:25 UTC
72644ef2f2ec7a274f79f6b32320d62757521329
2 files changed
+45
-1
packages/react/src/__tests__/ReactChildren-test.js
+39
@@ -868,6 +868,45 @@ describe('ReactChildren', () => {
868
]);
869
});
870
871
+ it('should warn for flattened children lists', async () => {
872
+ function ComponentRenderingFlattenedChildren({children}) {
873
+ return <div>{React.Children.toArray(children)}</div>;
874
+ }
875
+
876
+ const container = document.createElement('div');
877
+ const root = ReactDOMClient.createRoot(container);
878
+ await expect(async () => {
879
+ await act(() => {
880
+ root.render(
881
+ <ComponentRenderingFlattenedChildren>
882
+ {[<div />]}
883
+ </ComponentRenderingFlattenedChildren>,
884
+ );
885
+ });
886
+ }).toErrorDev([
887
+ 'Warning: Each child in a list should have a unique "key" prop.',
888
+ ]);
889
+ });
890
+
891
+ it('does not warn for flattened positional children', async () => {
892
+ function ComponentRenderingFlattenedChildren({children}) {
893
+ return <div>{React.Children.toArray(children)}</div>;
894
+ }
895
+
896
+ const container = document.createElement('div');
897
+ const root = ReactDOMClient.createRoot(container);
898
+ await expect(async () => {
899
+ await act(() => {
900
+ root.render(
901
+ <ComponentRenderingFlattenedChildren>
902
+ <div />
903
+ <div />
904
+ </ComponentRenderingFlattenedChildren>,
905
+ );
906
+ });
907
+ }).toErrorDev([]);
908
+ });
909
+
910
it('should escape keys', () => {
911
const zero = <div key="1" />;
912
const one = <div key="1=::=2" />;
packages/react/src/jsx/ReactJSXElement.js
+6
-1
@@ -953,7 +953,7 @@ export function createElement(type, config, children) {
953
}
954
955
export function cloneAndReplaceKey(oldElement, newKey) {
956
- return ReactElement(
956
+ const clonedElement = ReactElement(
957
oldElement.type,
958
newKey,
959
// When enableRefAsProp is on, this argument is ignored. This check only
@@ -966,6 +966,11 @@ export function cloneAndReplaceKey(oldElement, newKey) {
966
__DEV__ && enableOwnerStacks ? oldElement._debugStack : undefined,
967
__DEV__ && enableOwnerStacks ? oldElement._debugTask : undefined,
968
);
969
+ if (__DEV__) {
970
+ // The cloned element should inherit the original element's key validation.
971
+ clonedElement._store.validated = oldElement._store.validated;
972
+ }
973
+ return clonedElement;
974
}
975
976
/**