diff --git a/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts b/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts index 7770e2c265..ee0ccde565 100644 --- a/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts +++ b/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts @@ -47,9 +47,18 @@ export function column_getAutoFilterFn< const filterFns: Record> | undefined = column.table._rowModelFns.filterFns - const firstRow = column.table.getCoreRowModel().flatRows[0] + const rows = column.table.getCoreRowModel().flatRows + let value: unknown - const value = firstRow ? firstRow.getValue(column.id) : undefined + // Nullable columns should be inferred from their first available value, + // rather than changing filter behavior based on which row happens to lead. + for (let i = 0; i < rows.length; i++) { + const rowValue = rows[i]!.getValue(column.id) + if (rowValue !== null && rowValue !== undefined) { + value = rowValue + break + } + } let filterFnName: string diff --git a/packages/table-core/tests/implementation/features/column-filtering/createFilteredRowModel.test.ts b/packages/table-core/tests/implementation/features/column-filtering/createFilteredRowModel.test.ts index 1722c7934c..fa89603606 100644 --- a/packages/table-core/tests/implementation/features/column-filtering/createFilteredRowModel.test.ts +++ b/packages/table-core/tests/implementation/features/column-filtering/createFilteredRowModel.test.ts @@ -511,6 +511,22 @@ describe('createFilteredRowModel', () => { }) }) + it('should auto-filter a nullable string column when the first value is null', () => { + type NullableTestRow = { name: string | null } + const table = constructTable({ + features, + columns: [{ accessorKey: 'name', id: 'name' }], + data: [{ name: null }, { name: 'hello' }, { name: 'welcome' }], + initialState: { + columnFilters: [{ id: 'name', value: 'ell' }], + }, + }) + + expect( + table.getFilteredRowModel().rows.map((row) => row.original.name), + ).toEqual(['hello']) + }) + describe('unresolvable global filter fn', () => { afterEach(() => { vi.unstubAllEnvs() diff --git a/packages/table-core/tests/unit/features/column-filtering/columnFilteringFeature.utils.test.ts b/packages/table-core/tests/unit/features/column-filtering/columnFilteringFeature.utils.test.ts index e3686d8b15..b944d5f6c6 100644 --- a/packages/table-core/tests/unit/features/column-filtering/columnFilteringFeature.utils.test.ts +++ b/packages/table-core/tests/unit/features/column-filtering/columnFilteringFeature.utils.test.ts @@ -4,6 +4,7 @@ import { constructTable, filterFn_arrIncludes, filterFn_equals, + filterFn_includesString, filterFns, } from '../../../../src' import { @@ -64,6 +65,19 @@ describe('column_getAutoFilterFn', () => { expect(column_getAutoFilterFn(column)).toBe(filterFn_equals) }) + + it('infers the filter from the first non-null column value', () => { + type NullableSample = { name: string | null } + const table = constructTable({ + features, + columns: [{ accessorKey: 'name', id: 'name' }], + data: [{ name: null }, { name: 'hello' }] satisfies Array, + }) + + expect(column_getAutoFilterFn(table.getColumn('name')!)).toBe( + filterFn_includesString, + ) + }) }) // ---------------------------------------------------------------------------