diff --git a/src/column-layout/flexible-column-layout/index.tsx b/src/column-layout/flexible-column-layout/index.tsx index c80c38f2d0..270b60a98a 100644 --- a/src/column-layout/flexible-column-layout/index.tsx +++ b/src/column-layout/flexible-column-layout/index.tsx @@ -1,11 +1,11 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import React from 'react'; -import flattenChildren from 'react-keyed-flatten-children'; import clsx from 'clsx'; import { useContainerQuery } from '@cloudscape-design/component-toolkit'; +import { flattenChildren } from '../../internal/utils/flatten-children'; import { InternalColumnLayoutProps } from '../interfaces'; import styles from './styles.css.js'; @@ -68,7 +68,7 @@ export default function FlexibleColumnLayout({ > {flattenedChildren.map((child, i) => { // If this react child is a primitive value, the key will be undefined - const key = (child as Record<'key', unknown>).key; + const key = child && typeof child === 'object' ? (child as Record<'key', unknown>).key : undefined; return (
{flattenedChildren.map((child, i) => { // If this react child is a primitive value, the key will be undefined - const key = (child as Record<'key', unknown>).key; + const key = child && typeof child === 'object' ? (child as Record<'key', unknown>).key : undefined; return (
{ + const nestedArrayChildren = [ +
Item 1
, + [
Item 2
,
Item 3
], +
Item 4
, + ]; + + const fragmentChildren = [ + + A + B + , + C, + ]; + + const singleFragment = ( + + A + B + + ); + + // Tests React 19+ behavior: uses Children.toArray() which does NOT flatten fragments + describe('React 19+', () => { + beforeEach(() => { + // Mock React.version to trigger Children.toArray() code path + Object.defineProperty(React, 'version', { + value: '19.0.0', + writable: true, + configurable: true, + }); + }); + + it('flattens nested arrays', () => { + expect(flattenChildren(nestedArrayChildren)).toHaveLength(4); + }); + + it('does NOT flatten fragments', () => { + expect(flattenChildren(fragmentChildren)).toHaveLength(2); + expect(flattenChildren(singleFragment)).toHaveLength(1); + }); + }); + + // Tests React 16-18 behavior: uses react-keyed-flatten-children which DOES flatten fragments + describe('React 16-18', () => { + beforeEach(() => { + // Mock React.version to trigger react-keyed-flatten-children code path + Object.defineProperty(React, 'version', { + value: '18.2.0', + writable: true, + configurable: true, + }); + }); + + it('flattens nested arrays', () => { + expect(flattenChildren(nestedArrayChildren)).toHaveLength(4); + }); + + it('flattens fragments', () => { + expect(flattenChildren(fragmentChildren)).toHaveLength(3); + expect(flattenChildren(singleFragment)).toHaveLength(2); + }); + }); +}); diff --git a/src/internal/utils/flatten-children.ts b/src/internal/utils/flatten-children.ts new file mode 100644 index 0000000000..a14b594e80 --- /dev/null +++ b/src/internal/utils/flatten-children.ts @@ -0,0 +1,17 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React, { ReactNode } from 'react'; +import flattenChildrenLegacy from 'react-keyed-flatten-children'; + +export function flattenChildren(children: ReactNode): ReactNode[] { + const versionString = React.version?.split('.')[0]; + const majorVersion = versionString ? parseInt(versionString, 10) : NaN; + + if (!Number.isNaN(majorVersion) && majorVersion < 19) { + // React 16-18: Use react-keyed-flatten-children for backward compatibility + return flattenChildrenLegacy(children); + } + // React 19+: Uses Children.toArray() which doesn't flatten fragments. + // This also supports bigint which is not available in earlier React versions. + return React.Children.toArray(children); +} diff --git a/src/space-between/internal.tsx b/src/space-between/internal.tsx index f15e362fb2..b717f19ff7 100644 --- a/src/space-between/internal.tsx +++ b/src/space-between/internal.tsx @@ -1,13 +1,13 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import React, { forwardRef } from 'react'; -import flattenChildren from 'react-keyed-flatten-children'; import clsx from 'clsx'; import { useMergeRefs } from '@cloudscape-design/component-toolkit/internal'; import { getBaseProps } from '../internal/base-component'; import { InternalBaseComponentProps } from '../internal/hooks/use-base-component'; +import { flattenChildren } from '../internal/utils/flatten-children'; import WithNativeAttributes from '../internal/utils/with-native-attributes'; import { SpaceBetweenProps } from './interfaces'; @@ -52,10 +52,11 @@ const InternalSpaceBetween = forwardRef( ref={mergedRef} > {flattenedChildren.map(child => { - const key = typeof child === 'object' ? child.key : undefined; + // If this react child is a primitive value, the key will be undefined + const key = child && typeof child === 'object' ? (child as Record<'key', unknown>).key : undefined; return ( -
+
{child}
);