diff --git a/.changeset/shaky-hornets-train.md b/.changeset/shaky-hornets-train.md new file mode 100644 index 00000000000..46e7dfb90ed --- /dev/null +++ b/.changeset/shaky-hornets-train.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +feat(Datatable): add optional getRowId prop to support custom row identifiers diff --git a/packages/react/src/DataTable/DataTable.tsx b/packages/react/src/DataTable/DataTable.tsx index e2d59e171a9..275c693e872 100644 --- a/packages/react/src/DataTable/DataTable.tsx +++ b/packages/react/src/DataTable/DataTable.tsx @@ -50,6 +50,19 @@ export type DataTableProps = { * currently sorted column */ initialSortDirection?: Exclude + + /** + * Provide a function to determine the unique identifier for each row. + * This function allows you to customize the key used for the row. + * By default, the table uses the `id` field from the data. + * @param rowData The row data object for which the ID is being retrieved. + * @returns The unique identifier for the row, which can be a string or number. + */ + getRowId?: (rowData: Data) => string | number +} + +function defaultGetRowId(row: D) { + return row.id } function DataTable({ @@ -60,12 +73,14 @@ function DataTable({ data, initialSortColumn, initialSortDirection, + getRowId = defaultGetRowId, }: DataTableProps) { const {headers, rows, actions, gridTemplateColumns} = useTable({ data, columns, initialSortColumn, initialSortDirection, + getRowId, }) return ( diff --git a/packages/react/src/DataTable/__tests__/DataTable.test.tsx b/packages/react/src/DataTable/__tests__/DataTable.test.tsx index 1a068841a09..ebdb98f2610 100644 --- a/packages/react/src/DataTable/__tests__/DataTable.test.tsx +++ b/packages/react/src/DataTable/__tests__/DataTable.test.tsx @@ -1,10 +1,10 @@ import userEvent from '@testing-library/user-event' -import {render, screen, getByRole, queryByRole, queryAllByRole} from '@testing-library/react' +import {render, screen, getByRole, queryByRole, queryAllByRole, renderHook} from '@testing-library/react' import React from 'react' import {DataTable, Table} from '../../DataTable' import type {Column} from '../column' import {createColumnHelper} from '../column' -import {getGridTemplateFromColumns} from '../useTable' +import {getGridTemplateFromColumns, useTable} from '../useTable' describe('DataTable', () => { it('should render a semantic through `data` and `columns`', () => { @@ -1064,4 +1064,44 @@ describe('DataTable', () => { }) }) }) + + it('overrides row.id with result of getRowId function', () => { + const data = [ + {id: 1, name: 'Sabine', _uid: 'abc123'}, + {id: 2, name: 'The Matador', _uid: 'abc12334'}, + ] + + const getRowId = (row: {id: number; name: string; _uid: string}) => row._uid + + const {result} = renderHook(() => + useTable({ + data, + columns: [], + getRowId, + }), + ) + + expect(result.current.rows[0].id).toBe('abc123') + expect(result.current.rows[1].id).toBe('abc12334') + }) + + it('uses default row.id when getRowId is not provided', () => { + const data = [ + {id: 1, name: 'Sabine', _uid: 'abc123'}, + {id: 2, name: 'The Matador', _uid: 'abc12334'}, + ] + + const getRowId = (row: {id: number; name: string; _uid: string}) => row.id + + const {result} = renderHook(() => + useTable({ + data, + columns: [], + getRowId, + }), + ) + + expect(result.current.rows[0].id).toBe('1') + expect(result.current.rows[1].id).toBe('2') + }) }) diff --git a/packages/react/src/DataTable/useTable.ts b/packages/react/src/DataTable/useTable.ts index 28fd042b2a4..a2f52ff0919 100644 --- a/packages/react/src/DataTable/useTable.ts +++ b/packages/react/src/DataTable/useTable.ts @@ -9,6 +9,7 @@ interface TableConfig { data: Array initialSortColumn?: string | number initialSortDirection?: Exclude + getRowId: (rowData: Data) => string | number } interface Table { @@ -47,6 +48,7 @@ export function useTable({ data, initialSortColumn, initialSortDirection, + getRowId, }: TableConfig): Table { const [rowOrder, setRowOrder] = useState(data) const [prevData, setPrevData] = useState(data) @@ -181,15 +183,16 @@ export function useTable({ return { headers, rows: rowOrder.map(row => { + const rowId = getRowId(row) return { - id: `${row.id}`, + id: `${rowId}`, getValue() { return row }, getCells() { return headers.map(header => { return { - id: `${row.id}:${header.id}`, + id: `${rowId}:${header.id}`, column: header.column, rowHeader: header.column.rowHeader ?? false, getValue() {