Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-grouped-column-aggregation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/table-core': patch
---

fix(grouping): aggregate grouped columns at shallower group levels instead of showing the first row's value
8 changes: 6 additions & 2 deletions packages/table-core/src/utils/getGroupedRowModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,12 @@ export function getGroupedRowModel<TData extends RowData>(): (
subRows,
leafRows,
getValue: (columnId: string) => {
// Don't aggregate columns that are in the grouping
if (existingGrouping.includes(columnId)) {
// Don't aggregate columns that group this row at the current
// level or an ancestor level - their value is constant across
// the group. Columns grouped at a deeper level still need to
// be aggregated here.
const groupingIndex = existingGrouping.indexOf(columnId)
if (groupingIndex > -1 && groupingIndex <= depth) {
Comment on lines +95 to +100
if (row._valuesCache.hasOwnProperty(columnId)) {
return row._valuesCache[columnId]
}
Expand Down
50 changes: 50 additions & 0 deletions packages/table-core/tests/getGroupedRowModel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ import { createTable } from '../src/core/table'
import { getGroupedRowModel } from '../src/utils/getGroupedRowModel'
import { makeData, Person } from './makeTestData'

function createPerson(firstName: string, age: number): Person {
return {
firstName,
lastName: 'Doe',
age,
visits: 0,
progress: 0,
status: 'single',
}
}
Comment on lines +8 to +17

type personKeys = keyof Person
type PersonColumn = ColumnDef<Person, string | number | Person[] | undefined>

Expand Down Expand Up @@ -49,4 +60,43 @@ describe('#getGroupedRowModel', () => {
).toEqual(50000)
expect(end.valueOf() - start.valueOf()).toBeLessThan(5000)
})

it('aggregates a secondary grouped column at parent group levels', () => {
const data: Person[] = [
// first Engineering row is intentionally not the min, so the bug
// (returning the first row's raw value) differs from the aggregate
createPerson('Engineering', 30),
createPerson('Engineering', 24),
createPerson('Sales', 40),
]

const columnHelper = createColumnHelper<Person>()
const columns = [
columnHelper.accessor('firstName', { id: 'firstName' }),
columnHelper.accessor('age', { id: 'age', aggregationFn: 'min' }),
]

const table = createTable<Person>({
onStateChange() {},
renderFallbackValue: '',
data,
state: { grouping: ['firstName', 'age'] },
columns,
getCoreRowModel: getCoreRowModel(),
getGroupedRowModel: getGroupedRowModel(),
})

const rowsById = table.getGroupedRowModel().rowsById
const engineering = rowsById['firstName:Engineering']

// `age` is grouped at a deeper level, so the Engineering group row should
// still expose the aggregated (min) age across its leaf rows.
expect(engineering?.getValue('age')).toBe(24)
expect(rowsById['firstName:Sales']?.getValue('age')).toBe(40)

// at the `age` group level the column is the grouping key, so it keeps the
// grouping value rather than aggregating
expect(rowsById['firstName:Engineering>age:24']?.getValue('age')).toBe(24)
expect(rowsById['firstName:Engineering>age:30']?.getValue('age')).toBe(30)
})
})
Loading