Skip to content
Merged
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
8 changes: 7 additions & 1 deletion docs/api/features/sorting.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,22 @@ 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`
- 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

## Column API

### `getAutoSortingFn`
Expand Down
6 changes: 5 additions & 1 deletion docs/guide/sorting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
]
```
Expand Down
9 changes: 5 additions & 4 deletions examples/react/pagination/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ function Filter({
const columnFilterValue = column.getFilterValue()

return typeof firstValue === 'number' ? (
<div className="flex space-x-2">
<div className="flex space-x-2" onClick={e => e.stopPropagation()}>
<input
type="number"
value={(columnFilterValue as [number, number])?.[0] ?? ''}
Expand Down Expand Up @@ -279,11 +279,12 @@ function Filter({
</div>
) : (
<input
type="text"
value={(columnFilterValue ?? '') as string}
className="w-36 border shadow rounded"
onChange={e => column.setFilterValue(e.target.value)}
onClick={e => e.stopPropagation()}
placeholder={`Search...`}
className="w-36 border shadow rounded"
type="text"
value={(columnFilterValue ?? '') as string}
/>
)
}
Expand Down
57 changes: 42 additions & 15 deletions examples/react/sorting/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Person> = (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]

Expand All @@ -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: () => <span>Last Name</span>,
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: () => <span>Visits</span>,
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 (
<div className="p-2">
<div className="h-2" />
Expand Down
12 changes: 7 additions & 5 deletions examples/react/sorting/src/makeData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
}
Expand All @@ -22,23 +23,24 @@ 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<Person['status']>([
'relationship',
'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,
Expand Down
2 changes: 1 addition & 1 deletion packages/table-core/src/features/RowSorting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export interface SortingColumnDef<TData extends RowData> {
* @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<TData extends RowData> {
Expand Down
11 changes: 7 additions & 4 deletions packages/table-core/src/utils/getSortedRowModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function getSortedRowModel<TData extends RowData>(): (
const columnInfoById: Record<
string,
{
sortUndefined?: false | -1 | 1
sortUndefined?: false | -1 | 1 | 'first' | 'last'
invertSorting?: boolean
sortingFn: SortingFn<TData>
}
Expand All @@ -51,25 +51,28 @@ export function getSortedRowModel<TData extends RowData>(): (
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)

const aUndefined = aValue === undefined
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
}
}

Expand Down