diff --git a/docs/api/features/sorting.md b/docs/api/features/sorting.md index 1e755889ad..e5b60eea50 100644 --- a/docs/api/features/sorting.md +++ b/docs/api/features/sorting.md @@ -123,9 +123,13 @@ Inverts the order of the sorting for this column. This is useful for values that ### `sortUndefined` ```tsx -sortUndefined?: false | -1 | 1 // defaults to 1 +sortUndefined?: 'first' | 'last' | false | -1 | 1 // defaults to 1 ``` +- `'first'` + - Undefined values will be pushed to the beginning of the list +- `'last'` + - Undefined values will be pushed to the end of the list - `false` - Undefined values will be considered tied and need to be sorted by the next column filter or original index (whichever applies) - `-1` @@ -133,6 +137,8 @@ sortUndefined?: false | -1 | 1 // defaults to 1 - `1` - Undefined values will be sorted with lower priority (descending) (if ascending, undefined will appear on the end of the list) +> NOTE: `'first'` and `'last'` options are new in v8.16.0 + ## Column API ### `getAutoSortingFn` diff --git a/docs/guide/sorting.md b/docs/guide/sorting.md index 8aea90fecc..8e48cc5bad 100644 --- a/docs/guide/sorting.md +++ b/docs/guide/sorting.md @@ -284,16 +284,20 @@ Any undefined or nullish values will be sorted to the beginning or end of the li In not specified, the default value for `sortUndefined` is `1`, and undefined values will be sorted with lower priority (descending), if ascending, undefined will appear on the end of the list. +- `'first'` - Undefined values will be pushed to the beginning of the list +- `'last'` - Undefined values will be pushed to the end of the list - `false` - Undefined values will be considered tied and need to be sorted by the next column filter or original index (whichever applies) - `-1` - Undefined values will be sorted with higher priority (ascending) (if ascending, undefined will appear on the beginning of the list) - `1` - Undefined values will be sorted with lower priority (descending) (if ascending, undefined will appear on the end of the list) +> NOTE: `'first'` and `'last'` options are new in v8.16.0 + ```jsx const columns = [ { header: () => 'Rank', accessorKey: 'rank', - sortUndefined: -1, // 1 | -1 | false + sortUndefined: -1, // 'first' | 'last' | 1 | -1 | false }, ] ``` diff --git a/examples/react/pagination/src/main.tsx b/examples/react/pagination/src/main.tsx index 700672575b..0da85368f4 100644 --- a/examples/react/pagination/src/main.tsx +++ b/examples/react/pagination/src/main.tsx @@ -251,7 +251,7 @@ function Filter({ const columnFilterValue = column.getFilterValue() return typeof firstValue === 'number' ? ( -
+
e.stopPropagation()}> ) : ( column.setFilterValue(e.target.value)} + onClick={e => e.stopPropagation()} placeholder={`Search...`} - className="w-36 border shadow rounded" + type="text" + value={(columnFilterValue ?? '') as string} /> ) } diff --git a/examples/react/sorting/src/main.tsx b/examples/react/sorting/src/main.tsx index 02da2c401a..3a5ba39de4 100644 --- a/examples/react/sorting/src/main.tsx +++ b/examples/react/sorting/src/main.tsx @@ -8,11 +8,20 @@ import { flexRender, getCoreRowModel, getSortedRowModel, + SortingFn, SortingState, useReactTable, } from '@tanstack/react-table' import { makeData, Person } from './makeData' +//custom sorting logic for one of our enum columns +const sortStatusFn: SortingFn = (rowA, rowB, _columnId) => { + const statusA = rowA.original.status + const statusB = rowB.original.status + const statusOrder = ['single', 'complicated', 'relationship'] + return statusOrder.indexOf(statusA) - statusOrder.indexOf(statusB) +} + function App() { const rerender = React.useReducer(() => ({}), {})[1] @@ -23,60 +32,78 @@ function App() { { accessorKey: 'firstName', cell: info => info.getValue(), - footer: props => props.column.id, + //this column will sort in ascending order by default since it is a string column }, { accessorFn: row => row.lastName, id: 'lastName', cell: info => info.getValue(), header: () => Last Name, - footer: props => props.column.id, + sortUndefined: 'last', //force undefined values to the end + sortDescFirst: false, //first sort order will be ascending (nullable values can mess up auto detection of sort order) }, { accessorKey: 'age', header: () => 'Age', - footer: props => props.column.id, + //this column will sort in descending order by default since it is a number column }, { accessorKey: 'visits', header: () => Visits, - footer: props => props.column.id, + sortUndefined: 'last', //force undefined values to the end }, { accessorKey: 'status', header: 'Status', - footer: props => props.column.id, + sortingFn: sortStatusFn, //use our custom sorting function for this enum column }, { accessorKey: 'progress', header: 'Profile Progress', - footer: props => props.column.id, - sortDescFirst: true, // This column will sort in descending order first (default for number columns anyway) + // enableSorting: false, //disable sorting for this column + }, + { + accessorKey: 'rank', + header: 'Rank', + invertSorting: true, //invert the sorting order (golf score-like where smaller is better) }, { accessorKey: 'createdAt', header: 'Created At', - // sortingFn: 'datetime' (inferred from the data) + // sortingFn: 'datetime' //make sure table knows this is a datetime column (usually can detect if no null values) }, ], [] ) - const [data, setData] = React.useState(() => makeData(10_000)) - const refreshData = () => setData(() => makeData(10_000)) + const [data, setData] = React.useState(() => makeData(1_000)) + const refreshData = () => setData(() => makeData(100_000)) //stress test with 100k rows const table = useReactTable({ - data, columns, + data, + debugTable: true, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), //client-side sorting + onSortingChange: setSorting, //optionally control sorting state in your own scope for easy access + // sortingFns: { + // sortStatusFn, //or provide our custom sorting function globally for all columns to be able to use + // }, + //no need to pass pageCount or rowCount with client-side pagination as it is calculated automatically state: { sorting, }, - onSortingChange: setSorting, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - debugTable: true, + // autoResetPageIndex: false, // turn off page index reset when sorting or filtering - default on/true + // enableMultiSort: false, //Don't allow shift key to sort multiple columns - default on/true + // enableSorting: false, // - default on/true + // enableSortingRemoval: false, //Don't allow - default on/true + // isMultiSortEvent: (e) => true, //Make all clicks multi-sort - default requires `shift` key + // maxMultiSortColCount: 3, // only allow 3 columns to be sorted at once - default is Infinity }) + //access sorting state from the table instance + console.log(table.getState().sorting) + return (
diff --git a/examples/react/sorting/src/makeData.ts b/examples/react/sorting/src/makeData.ts index f664a9a994..d6c0639b22 100644 --- a/examples/react/sorting/src/makeData.ts +++ b/examples/react/sorting/src/makeData.ts @@ -2,11 +2,12 @@ import { faker } from '@faker-js/faker' export type Person = { firstName: string - lastName: string + lastName: string | undefined age: number - visits: number + visits: number | undefined progress: number status: 'relationship' | 'complicated' | 'single' + rank: number createdAt: Date subRows?: Person[] } @@ -22,9 +23,9 @@ const range = (len: number) => { const newPerson = (): Person => { return { firstName: faker.person.firstName(), - lastName: faker.person.lastName(), + lastName: Math.random() < 0.1 ? undefined : faker.person.lastName(), age: faker.number.int(40), - visits: faker.number.int(1000), + visits: Math.random() < 0.1 ? undefined : faker.number.int(1000), progress: faker.number.int(100), createdAt: faker.date.anytime(), status: faker.helpers.shuffle([ @@ -32,13 +33,14 @@ const newPerson = (): Person => { 'complicated', 'single', ])[0]!, + rank: faker.number.int(100), } } export function makeData(...lens: number[]) { const makeDataLevel = (depth = 0): Person[] => { const len = lens[depth]! - return range(len).map((d): Person => { + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/packages/table-core/src/features/RowSorting.ts b/packages/table-core/src/features/RowSorting.ts index 3d127b9a10..c2e7c32d53 100644 --- a/packages/table-core/src/features/RowSorting.ts +++ b/packages/table-core/src/features/RowSorting.ts @@ -90,7 +90,7 @@ export interface SortingColumnDef { * @link [API Docs](https://tanstack.com/table/v8/docs/api/features/sorting#sortundefined) * @link [Guide](https://tanstack.com/table/v8/docs/guide/sorting) */ - sortUndefined?: false | -1 | 1 + sortUndefined?: false | -1 | 1 | 'first' | 'last' } export interface SortingColumn { diff --git a/packages/table-core/src/utils/getSortedRowModel.ts b/packages/table-core/src/utils/getSortedRowModel.ts index 05aebf16b7..eb5e9d5729 100644 --- a/packages/table-core/src/utils/getSortedRowModel.ts +++ b/packages/table-core/src/utils/getSortedRowModel.ts @@ -25,7 +25,7 @@ export function getSortedRowModel(): ( const columnInfoById: Record< string, { - sortUndefined?: false | -1 | 1 + sortUndefined?: false | -1 | 1 | 'first' | 'last' invertSorting?: boolean sortingFn: SortingFn } @@ -51,12 +51,13 @@ export function getSortedRowModel(): ( for (let i = 0; i < availableSorting.length; i += 1) { const sortEntry = availableSorting[i]! const columnInfo = columnInfoById[sortEntry.id]! + const sortUndefined = columnInfo.sortUndefined const isDesc = sortEntry?.desc ?? false let sortInt = 0 // All sorting ints should always return in ascending order - if (columnInfo.sortUndefined) { + if (sortUndefined) { const aValue = rowA.getValue(sortEntry.id) const bValue = rowB.getValue(sortEntry.id) @@ -64,12 +65,14 @@ export function getSortedRowModel(): ( const bUndefined = bValue === undefined if (aUndefined || bUndefined) { + if (sortUndefined === 'first') return aUndefined ? -1 : 1 + if (sortUndefined === 'last') return aUndefined ? 1 : -1 sortInt = aUndefined && bUndefined ? 0 : aUndefined - ? columnInfo.sortUndefined - : -columnInfo.sortUndefined + ? sortUndefined + : -sortUndefined } }