From 11711ff1cf9c57fc172b4507b80dfca41cf518e4 Mon Sep 17 00:00:00 2001 From: okxint Date: Tue, 13 Oct 2026 10:47:06 +0530 Subject: [PATCH 1/2] fix(table-core): serialize array values as map keys in getFacetedUniqueValues Array cell values use reference equality in the Map, so two rows with identical array contents (e.g. ['a','b']) are counted as distinct entries instead of being aggregated. Serialize arrays with JSON.stringify before using them as map keys so equal-content arrays get the same bucket. Fixes #5405 --- .../createFacetedUniqueValues.ts | 5 ++-- .../columnFacetingFeature.test.ts | 29 +++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts b/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts index 85e2bcc304..42543a8427 100644 --- a/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts +++ b/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts @@ -83,9 +83,10 @@ function _createFacetedUniqueValues< for (let j = 0; j < values.length; j++) { const value = values[j] - const previousValue = facetedUniqueValues.get(value) + const mapKey = Array.isArray(value) ? JSON.stringify(value) : value + const previousValue = facetedUniqueValues.get(mapKey) facetedUniqueValues.set( - value, + mapKey, previousValue === undefined ? 1 : previousValue + 1, ) } diff --git a/packages/table-core/tests/unit/features/column-faceting/columnFacetingFeature.test.ts b/packages/table-core/tests/unit/features/column-faceting/columnFacetingFeature.test.ts index ade1f20e91..daee5520d6 100644 --- a/packages/table-core/tests/unit/features/column-faceting/columnFacetingFeature.test.ts +++ b/packages/table-core/tests/unit/features/column-faceting/columnFacetingFeature.test.ts @@ -23,6 +23,7 @@ import type { ColumnDef } from '../../../../src' type Person = { firstName: string status: string | undefined + tags?: Array } const features = testFeatures({ @@ -45,12 +46,16 @@ const columns: Array> = [ filterFn: 'equalsString', id: 'status', }, + { + accessorKey: 'tags', + id: 'tags', + }, ] const data: Array = [ - { firstName: 'Alice', status: 'active' }, - { firstName: 'Bob', status: undefined }, - { firstName: 'Carol', status: 'active' }, + { firstName: 'Alice', status: 'active', tags: ['a', 'b'] }, + { firstName: 'Bob', status: undefined, tags: ['a', 'b'] }, + { firstName: 'Carol', status: 'active', tags: ['c'] }, ] function makeTable(columnFilters: Array<{ id: string; value: unknown }>) { @@ -81,8 +86,8 @@ describe('column faceting row model', () => { expect( firstNameColumn.getFacetedRowModel().rows.map((row) => row.original), ).toEqual([ - { firstName: 'Alice', status: 'active' }, - { firstName: 'Carol', status: 'active' }, + { firstName: 'Alice', status: 'active', tags: ['a', 'b'] }, + { firstName: 'Carol', status: 'active', tags: ['c'] }, ]) }) @@ -98,6 +103,20 @@ describe('column faceting row model', () => { ) }) + it('counts array cell values by content rather than reference', () => { + const table = makeTable([]) + const tagsColumn = table.getColumn('tags')! + + // ['a','b'] appears in two rows with different array references — + // without JSON.stringify as the map key both would get count 1 + expect(tagsColumn.getFacetedUniqueValues()).toEqual( + new Map([ + [JSON.stringify(['a', 'b']), 2], + [JSON.stringify(['c']), 1], + ]), + ) + }) + it('caches faceted factory functions per column and global context', () => { const facetedRowModel = vi.fn( (table: any, _columnId: string) => () => table.getPreFilteredRowModel(), From 2a5f023083d4ac02dc0f7cf3a415644faaeae8b2 Mon Sep 17 00:00:00 2001 From: okxint Date: Sun, 18 Oct 2026 14:46:15 +0530 Subject: [PATCH 2/2] fix(table-core): prevent array key collision and restore lookup contract Use an ArrayKeyMap subclass that prefixes serialised array keys with a null byte so the string '["a","b"]' and the array ['a','b'] never collide. Callers can now pass the original array value to .get() and .has() and get the correct count back. --- .../createFacetedUniqueValues.ts | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts b/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts index 42543a8427..75e907dfbb 100644 --- a/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts +++ b/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts @@ -71,7 +71,7 @@ function _createFacetedUniqueValues< .map((column) => column.id) : [columnId] - const facetedUniqueValues = new Map() + const facetedUniqueValues = new ArrayKeyMap() for (let i = 0; i < flatRows.length; i++) { for (let c = 0; c < columnIds.length; c++) { @@ -83,10 +83,9 @@ function _createFacetedUniqueValues< for (let j = 0; j < values.length; j++) { const value = values[j] - const mapKey = Array.isArray(value) ? JSON.stringify(value) : value - const previousValue = facetedUniqueValues.get(mapKey) + const previousValue = facetedUniqueValues.get(value) facetedUniqueValues.set( - mapKey, + value, previousValue === undefined ? 1 : previousValue + 1, ) } @@ -95,3 +94,26 @@ function _createFacetedUniqueValues< return facetedUniqueValues } + +/** + * A Map subclass that normalises array keys so that: + * - Two distinct array instances with the same contents map to the same entry. + * - A serialised string like `'["a","b"]'` never collides with an array `['a','b']`. + * + * Internally arrays are stored under a null-byte-prefixed JSON key, which cannot + * appear in ordinary strings. + */ +class ArrayKeyMap extends Map { + static toKey(key: unknown): unknown { + return Array.isArray(key) ? '\0' + JSON.stringify(key) : key + } + override get(key: unknown): number | undefined { + return super.get(ArrayKeyMap.toKey(key)) + } + override set(key: unknown, value: number): this { + return super.set(ArrayKeyMap.toKey(key), value) + } + override has(key: unknown): boolean { + return super.has(ArrayKeyMap.toKey(key)) + } +}