fix(ui)!: remove Fragment wrapper in Table to prevent id prop error

React Aria's Collection component injects an `id` prop into rendered
children. When children were wrapped in Fragment, this caused
"Invalid prop `id` supplied to `React.Fragment`" errors on render.

BREAKING CHANGE: The `cell` and `header` properties in `ColumnConfig`
now return `ReactElement` instead of `ReactNode`. Cell and header
functions that return non-element values must be updated to always
return a valid element.

Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
Johan Persson
2026-01-22 12:14:15 +01:00
parent 24eb7d7933
commit caeb9ada1d
4 changed files with 30 additions and 10 deletions
+22
View File
@@ -0,0 +1,22 @@
---
'@backstage/ui': minor
---
**BREAKING**: The `cell` and `header` properties in `ColumnConfig` now return `ReactElement` instead of `ReactNode`.
This fixes an issue where React Aria's Collection component would inject an `id` prop into Fragment wrappers, causing "Invalid prop `id` supplied to `React.Fragment`" errors on render.
Migration:
```diff
const columns: ColumnConfig<MyItem>[] = [
{
id: 'name',
label: 'Name',
- cell: (item) => item.name,
+ cell: (item) => <CellText title={item.name} />,
- header: () => 'Name',
+ header: () => <Column>Name</Column>,
},
];
```
+2 -2
View File
@@ -479,11 +479,11 @@ export const Column: (props: ColumnProps) => JSX_2.Element;
// @public (undocumented)
export interface ColumnConfig<T extends TableItem> {
// (undocumented)
cell: (item: T) => ReactNode;
cell: (item: T) => ReactElement;
// (undocumented)
defaultWidth?: ColumnSize | null;
// (undocumented)
header?: () => ReactNode;
header?: () => ReactElement;
// (undocumented)
id: string;
// (undocumented)
@@ -29,7 +29,7 @@ import type {
RowRenderFn,
TablePaginationType,
} from '../types';
import { Fragment, useMemo } from 'react';
import { useMemo } from 'react';
import { VisuallyHidden } from '../../VisuallyHidden';
import { Flex } from '../../Flex';
@@ -158,7 +158,7 @@ export function Table<T extends TableItem>({
<TableHeader columns={visibleColumns}>
{column =>
column.header ? (
<>{column.header()}</>
column.header()
) : (
<Column
id={column.id}
@@ -201,9 +201,7 @@ export function Table<T extends TableItem>({
: undefined
}
>
{column => (
<Fragment key={column.id}>{column.cell(item)}</Fragment>
)}
{column => column.cell(item)}
</Row>
);
}}
+3 -3
View File
@@ -19,7 +19,7 @@ import {
ColumnProps as ReactAriaColumnProps,
TableProps as ReactAriaTableProps,
} from 'react-aria-components';
import type { ReactNode } from 'react';
import type { ReactElement, ReactNode } from 'react';
import type { SortDescriptor as ReactStatelySortDescriptor } from 'react-stately';
import type { ColumnSize, ColumnStaticSize } from '@react-types/table';
import type { TextColors } from '../../types';
@@ -89,8 +89,8 @@ export type TablePaginationType = NoPagination | PagePagination;
export interface ColumnConfig<T extends TableItem> {
id: string;
label: string;
cell: (item: T) => ReactNode;
header?: () => ReactNode;
cell: (item: T) => ReactElement;
header?: () => ReactElement;
isSortable?: boolean;
isHidden?: boolean;
width?: ColumnSize | null;