Sorting
Add sortable: true to any column to enable client-side sorting on that column.
Clicking a header cycles through three states: ascending → descending → unsorted. A third click
removes the sort without resetting any other table state.
Columns without sortable are not interactive and are excluded from the tab order.
Enable sortMulti to let users sort by several columns at once — hold
Ctrl (or ⌘ on macOS) while clicking additional headers. See
Multi-column sorting below.
Sorting playground
Toggle the default sort column, direction, a custom icon, and per-column sort functions. The onSort readout shows the callback firing on every sort change.
import DataTable, { type TableColumn, SortOrder } from 'react-data-table-component';
const columns: TableColumn<Employee>[] = [
{ id: 'name', name: 'Name', selector: r => r.name, sortable: true },
{ id: 'dept', name: 'Department', selector: r => r.department, sortable: true },
{ id: 'salary', name: 'Salary', selector: r => r.salary, sortable: true,
format: r => `$${r.salary.toLocaleString()}`, right: true },
{ id: 'hired', name: 'Hired', selector: r => r.hired, sortable: true },
{ id: 'id', name: 'ID', selector: r => r.id }, // not sortable
];
<DataTable
columns={columns}
data={data}
defaultSortFieldId="name"
defaultSortAsc={true}
onSort={(column, direction) => console.log(column.id, direction)}
highlightOnHover
/> Removable sorting
Sorting is removable by default. Clicking a sortable header walks through three states so the user can clear a sort directly from the header instead of resetting the whole table:
- First click — sort ascending (or the direction set by
defaultSortAsc). - Second click — sort descending.
- Third click — remove the sort.
When the sort is removed, onSort fires with an empty column ({}),
the default direction, and an empty sortColumns array. The table returns to its
original (unsorted) row order. This is also the signal a server-side handler should use to drop
its ORDER BY clause — see Server-side sorting.
Multi-column sorting
Set sortMulti to let the user sort by more than one column. A plain click still
replaces the entire sort. Holding Ctrl (⌘ on macOS) while clicking a header
adds that column to the existing sort instead. Sort priority follows the order columns
are added, and a small numbered badge appears on each participating header.
Each column still cycles ascending → descending → off independently. Ctrl-clicking a column already in the sort flips its direction, then removes it on the next Ctrl-click. Removing the primary column promotes the next one to primary.
Multi-column sorting
Click a header to sort. Ctrl-click (⌘-click on macOS) a second header to add it. The readout shows the live sortColumns array in priority order.
Ctrl-click (⌘-click on macOS) a second column header to add it to the sort. Cycle each column ascending → descending → off.
import { useState } from 'react';
import DataTable, { type TableColumn, type SortColumn } from 'react-data-table-component';
const columns: TableColumn<Employee>[] = [
{ id: 'name', name: 'Name', selector: r => r.name, sortable: true },
{ id: 'department', name: 'Department', selector: r => r.department, sortable: true },
{ id: 'salary', name: 'Salary', selector: r => r.salary, sortable: true, right: true },
{ id: 'hired', name: 'Hired', selector: r => r.hired, sortable: true },
];
export default function App() {
const [sortColumns, setSortColumns] = useState<SortColumn<Employee>[]>([]);
return (
<DataTable
columns={columns}
data={data}
sortMulti
// onSort's 4th argument is the full sort config in priority order
onSort={(column, direction, sortedRows, sortColumns) => setSortColumns(sortColumns)}
highlightOnHover
/>
);
}
Multi-column sorting is stable: when every active sort column ties, rows keep their original
relative order. Each column honors its own sortFunction when one is set; otherwise
its selector value is compared.
Default sort
defaultSortFieldId sets which column is sorted on first render.
Its value must match the id on the column definition.
defaultSortAsc controls the initial direction; it defaults to true (ascending).
// Sort by salary descending on load
<DataTable
columns={columns}
data={data}
defaultSortFieldId="salary"
defaultSortAsc={false}
/> Listening to sort events
onSort fires on every sort change, whether triggered by clicking a header or by
a defaultSortFieldId change. It receives four arguments: the primary sorted column,
its direction, the fully sorted row array, and sortColumns — the complete sort
configuration in priority order.
For single-column sorting the first two arguments are all you need. The fourth argument is what
you read when sortMulti is enabled, and it is also how you detect a cleared sort
(the array is empty and the primary column is {}).
import { SortOrder, type SortColumn } from 'react-data-table-component';
<DataTable
columns={columns}
data={data}
onSort={(column, direction, sortedRows, sortColumns) => {
if (sortColumns.length === 0) {
console.log('sort cleared');
return;
}
console.log('primary sort:', column.id, direction);
// sortedRows is the full sorted array after the sort is applied.
// sortColumns holds every active sort column in priority order:
// [{ column, sortDirection }, ...]
}}
/> Custom sort function: per column
Override the sort comparator for a specific column with column.sortFunction.
It receives two row objects and returns a number, just like Array.prototype.sort.
Use this for locale-aware strings, currency values, or any non-default ordering.
const columns: TableColumn<Employee>[] = [
{
id: 'name',
name: 'Name',
selector: r => r.name,
sortable: true,
sortFunction: (a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }),
},
{
id: 'salary',
name: 'Salary',
selector: r => r.salary,
sortable: true,
sortFunction: (a, b) => a.salary - b.salary,
},
]; Custom sort function: table level
The table-level sortFunction prop replaces the entire sort algorithm for every
column. It receives the full row array, the selector function for the active column, and the
sort direction. Return the sorted array. This is less common than per-column sortFunction
but useful when you need a single unified sort strategy across all columns.
import { SortOrder, type SortFunction } from 'react-data-table-component';
const customSort: SortFunction<Employee> = (rows, selector, direction) => {
return [...rows].sort((a, b) => {
const aVal = selector(a);
const bVal = selector(b);
if (aVal === bVal) return 0;
const cmp = aVal > bVal ? 1 : -1;
return direction === SortOrder.ASC ? cmp : -cmp;
});
};
<DataTable columns={columns} data={data} sortFunction={customSort} /> Priority ordering: sort certain rows first
Sometimes you want specific rows to float to the top regardless of what column is sorted. There are two distinct patterns: sorting a column by a custom value order (e.g. status priority), and anchoring specific rows by identity regardless of sort. The latter is covered in depth on the Row pinning page.
Priority sort patterns
Two modes: sort a Status column by custom priority order (Active → On Leave → Terminated), and break ties within the same primary value using a secondary salary sort.
Sort Status by a custom priority: Active → On Leave → Terminated. Click the Status header.
import DataTable, { SortOrder, type TableColumn, type SortFunction } from 'react-data-table-component';
// ── Pattern 1: custom value ordering ───────────────────────────────────────
// Sort "Active → On Leave → Terminated" instead of alphabetically.
const STATUS_ORDER = { Active: 0, 'On Leave': 1, Terminated: 2 };
const columns: TableColumn<Employee>[] = [
{
id: 'status',
name: 'Status',
selector: r => STATUS_ORDER[r.status], // numeric selector drives the sort key
cell: r => <StatusBadge status={r.status} />,
sortable: true,
sortFunction: (a, b) => STATUS_ORDER[a.status] - STATUS_ORDER[b.status],
},
];
// ── Pattern 2: secondary sort (break ties) ──────────────────────────────────
// When two rows have the same primary value, sort by salary descending as a
// tiebreaker. Wire this as the table-level sortFunction.
const withSecondarySalary: SortFunction<Employee> = (rows, selector, direction) =>
[...rows].sort((a, b) => {
const av = selector(a), bv = selector(b);
const primary = av === bv ? 0 : (av > bv ? 1 : -1) * (direction === SortOrder.ASC ? 1 : -1);
return primary !== 0 ? primary : b.salary - a.salary; // salary desc as tiebreaker
}); Sorting null / empty values last
By default, empty strings and null sort ahead of real values alphabetically.
Push them to the bottom with a guard in your comparator:
const nullsLastSort: SortFunction<Employee> = (rows, selector, direction) =>
[...rows].sort((a, b) => {
const av = selector(a);
const bv = selector(b);
// Empty / null → always sink to bottom regardless of direction
const aEmpty = av === null || av === undefined || av === '';
const bEmpty = bv === null || bv === undefined || bv === '';
if (aEmpty && bEmpty) return 0;
if (aEmpty) return 1;
if (bEmpty) return -1;
const cmp = av > bv ? 1 : -1;
return direction === SortOrder.ASC ? cmp : -cmp;
});
<DataTable columns={columns} data={data} sortFunction={nullsLastSort} /> Custom sort icon
Pass any React node to sortIcon to replace the default chevron.
The icon is rendered inside a span that receives rdt_sortIconActive /
rdt_sortIconInactive and rdt_sortIconAsc class names. Use these to
style direction and active state with CSS.
// A simple triangle that flips via CSS
const SortIcon = () => (
<svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor" aria-hidden="true">
<path d="M5 1l4 8H1z" />
</svg>
);
// Flip the icon for descending with CSS:
// .rdt_sortIcon:not(.rdt_sortIconAsc) svg { transform: rotate(180deg); }
<DataTable columns={columns} data={data} sortIcon={<SortIcon />} /> Server-side sorting
Set sortServer to stop the table from sorting rows locally.
DataTable still updates its visual sort indicator and fires onSort. Your handler
is responsible for fetching or reordering data and passing the updated array back via
data.
When a column maps to a different backend field name, set sortField on the column.
The value is passed to onSort via the column object so you can forward it directly
to your query.
The removable and multi-column behaviors apply to server-side sorting too — the table sends the intent, your handler turns it into a query. Two cases to handle:
- Cleared sort: the three-state cycle means a header can now resolve to
no sort. When that happens
onSortfires with an emptysortColumnsarray and an empty primary column — drop yourORDER BYand fetch the default order. - Multi-column sort: with
sortMultienabled, read the fourthsortColumnsargument and build an ordered list of sort keys rather than relying on the single primary column.
function handleSort(column, direction, sortedRows, sortColumns) {
// Cleared sort → fetch default order
if (sortColumns.length === 0) {
return load({ orderBy: [] });
}
// Build an ordered list of { field, dir } from the full sort config
const orderBy = sortColumns.map(s => ({
field: s.column.sortField ?? String(s.column.id),
dir: s.sortDirection,
}));
load({ orderBy }); // e.g. ORDER BY department ASC, salary DESC
}
<DataTable columns={columns} data={data} sortServer sortMulti onSort={handleSort} /> Server-side sorting
Click any column header to trigger a fake server fetch (250 ms latency). The table shows a loading indicator while the sorted data arrives.
import { useState } from 'react';
import DataTable, { type TableColumn, SortOrder } from 'react-data-table-component';
const columns: TableColumn<Employee>[] = [
{ id: 'name', name: 'Name', selector: r => r.name, sortable: true, sortField: 'name' },
{ id: 'department', name: 'Department', selector: r => r.department, sortable: true, sortField: 'department' },
{ id: 'salary', name: 'Salary', selector: r => r.salary, sortable: true, sortField: 'salary',
right: true, format: r => `$${r.salary.toLocaleString()}` },
{ id: 'hired', name: 'Hired', selector: r => r.hired, sortable: true, sortField: 'hired' },
];
export default function App() {
const [data, setData] = useState<Employee[]>(initialData);
const [loading, setLoading] = useState(false);
async function handleSort(col: TableColumn<Employee>, dir: SortOrder) {
const field = col.sortField ?? String(col.id);
setLoading(true);
const sorted = await fetch(`/api/employees?sort=${field}&dir=${dir}`).then(r => r.json());
setData(sorted);
setLoading(false);
}
return (
<DataTable
columns={columns}
data={data}
progressPending={loading}
sortServer
onSort={handleSort}
highlightOnHover
/>
);
} Combined: server-side sorting and pagination
When using both sortServer and paginationServer, the three callbacks
are mutually exclusive. Only one fires per interaction.
Sorting resets the page to 1 automatically and onChangePage is suppressed, so
onSort is your only fetch point for a new sort. Always fetch from page 1 there
and update your local page state to 1 to stay in sync.
See the pagination docs for the complete callback contract.
Server-side sort + pagination
150-row simulated dataset. Sort any column, then page through the results. Sorting resets to page 1 automatically.
import { useState, useEffect, useCallback } from 'react';
import DataTable, { type TableColumn, SortOrder } from 'react-data-table-component';
export default function App() {
const [data, setData] = useState<Employee[]>([]);
const [loading, setLoading] = useState(true);
const [totalRows, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(10);
const [sortField, setSortField] = useState('name');
const [sortDir, setSortDir] = useState<SortOrder>(SortOrder.ASC);
const [resetPage, setResetPage] = useState(false);
const load = useCallback(async (params) => {
setLoading(true);
const result = await fetchFromServer(params);
setData(result.rows);
setTotal(result.total);
setLoading(false);
}, []);
useEffect(() => { load({ page, perPage, sortField, sortDir }); }, []);
function handleSort(col: TableColumn<Employee>, dir: SortOrder) {
const field = col.sortField ?? String(col.id);
setSortField(field); setSortDir(dir);
setPage(1); setResetPage(prev => !prev); // reset pagination to page 1 on sort
load({ page: 1, perPage, sortField: field, sortDir: dir });
}
return (
<DataTable
columns={columns}
data={data}
progressPending={loading}
sortServer
onSort={handleSort}
pagination
paginationServer
paginationTotalRows={totalRows}
paginationResetDefaultPage={resetPage}
onChangePage={p => { setPage(p); load({ page: p, perPage, sortField, sortDir }); }}
onChangeRowsPerPage={(pp, p) => { setPerPage(pp); load({ page: p, perPage: pp, sortField, sortDir }); }}
highlightOnHover
/>
);
} Resetting sort programmatically
Attach a ref to DataTable and call ref.current.clearSort() to reset
the sort back to its default state (defaultSortFieldId / defaultSortAsc,
or unsorted if no defaults are set). Useful when the user switches a view mode that makes the
current sort invalid.
Reset sort via ref
Sort any column, then click Reset sort to return to the default (Name ascending).
import { useRef } from 'react';
import DataTable, { type DataTableHandle } from 'react-data-table-component';
function App() {
const ref = useRef<DataTableHandle>(null);
return (
<>
<button onClick={() => ref.current?.clearSort()}>Reset sort</button>
<DataTable
ref={ref}
columns={columns}
data={data}
defaultSortFieldId="name"
defaultSortAsc={true}
/>
</>
);
} Prop reference
| Prop / method | Type | Default | Description |
|---|---|---|---|
defaultSortFieldId | string | number | - | Column id to sort by on first render. |
defaultSortAsc | boolean | true | Initial sort direction. false = descending. |
onSort | (column, direction, sortedRows, sortColumns) => void | - | Called on every sort change with the primary column, its direction, the sorted rows, and the full sortColumns config in priority order. An empty sortColumns array means the sort was cleared. |
sortMulti | boolean | false | Enable multi-column sorting. Ctrl/⌘-click a header to add it to the existing sort. |
sortServer | boolean | false | Disable client-side sorting. Use with onSort to sort remotely. |
sortFunction | SortFunction<T> | - | Table-level sort algorithm. Replaces the default sort for all columns. |
sortIcon | ReactNode | - | Custom icon rendered inside sortable column headers. |
column.sortable | boolean | false | Enable sorting on this column. |
column.sortFunction | (a, b) => number | - | Per-column sort comparator. Takes priority over the table-level sortFunction. |
column.sortField | string | - | Backend field name passed to onSort when it differs from column.id. |
ref.clearSort() | DataTableHandle | - | Imperatively reset sort to the default (defaultSortFieldId / defaultSortAsc), or unsorted if no defaults are set. See DataTableHandle. |