Overview
React Data Grid provides controlled sorting functionality. The grid handles UI interactions but you control the actual row ordering, giving you flexibility to implement server-side or client-side sorting.
Basic Setup
Enable sorting by marking columns as sortable and managing sort state:
import { useState } from 'react';
import { DataGrid, type Column, type SortColumn } from 'react-data-grid';
interface Row {
id: number;
name: string;
email: string;
}
const columns: readonly Column<Row>[] = [
{ key: 'id', name: 'ID', sortable: true },
{ key: 'name', name: 'Name', sortable: true },
{ key: 'email', name: 'Email', sortable: true }
];
function MyGrid() {
const [rows, setRows] = useState(initialRows);
const [sortColumns, setSortColumns] = useState<readonly SortColumn[]>([]);
return (
<DataGrid
columns={columns}
rows={rows}
sortColumns={sortColumns}
onSortColumnsChange={setSortColumns}
/>
);
}
Sorting is controlled: the grid does not reorder rows automatically. You must apply sorting logic to your rows based on sortColumns.
Column Configuration
Enable sorting for the column. Click the header to toggle sort direction.
Start with descending order on first click instead of ascending.const columns: Column<Row>[] = [
{
key: 'date',
name: 'Date',
sortable: true,
sortDescendingFirst: true // Click once for newest first
}
];
Sort Props
Array of currently sorted columns. Each entry contains:
columnKey: The column’s key
direction: Either 'ASC' or 'DESC'
onSortColumnsChange
(sortColumns: SortColumn[]) => void
Callback triggered when sorting changes.
Implementing Sort Logic
Apply sorting to your rows:
import { useMemo, useState } from 'react';
import { DataGrid, type SortColumn } from 'react-data-grid';
function MyGrid() {
const [rows, setRows] = useState(initialRows);
const [sortColumns, setSortColumns] = useState<readonly SortColumn[]>([]);
const sortedRows = useMemo(() => {
if (sortColumns.length === 0) return rows;
return [...rows].sort((a, b) => {
for (const sort of sortColumns) {
const { columnKey, direction } = sort;
const aValue = a[columnKey as keyof Row];
const bValue = b[columnKey as keyof Row];
if (aValue < bValue) {
return direction === 'ASC' ? -1 : 1;
}
if (aValue > bValue) {
return direction === 'ASC' ? 1 : -1;
}
}
return 0;
});
}, [rows, sortColumns]);
return (
<DataGrid
columns={columns}
rows={sortedRows}
sortColumns={sortColumns}
onSortColumnsChange={setSortColumns}
/>
);
}
Multi-Column Sorting
By default, the grid supports multi-column sorting with Ctrl + Click (or Cmd + Click on Mac):
// Click column header: Sort by that column
// Ctrl + Click another header: Add secondary sort
// Click sorted header again: Reverse direction
// Click sorted header third time: Remove from sort
The grid displays sort priority numbers when multiple columns are sorted.
Disable Multi-Column Sorting
Limit to single-column sorting:
function onSortColumnsChange(sortColumns: SortColumn[]) {
setSortColumns(sortColumns.slice(-1)); // Keep only the last sorted column
}
<DataGrid
columns={columns}
rows={rows}
sortColumns={sortColumns}
onSortColumnsChange={onSortColumnsChange}
/>
Sort Direction Types
type SortDirection = 'ASC' | 'DESC';
interface SortColumn {
readonly columnKey: string;
readonly direction: SortDirection;
}
Custom Sort Icons
Customize the sort indicator appearance:
import {
DataGrid,
renderSortIcon,
renderSortPriority,
type Renderers,
type RenderSortStatusProps
} from 'react-data-grid';
function CustomSortStatus(props: RenderSortStatusProps) {
return (
<div className="custom-sort">
{renderSortIcon(props)}
{renderSortPriority(props)}
</div>
);
}
const customRenderers: Renderers<Row, SummaryRow> = {
renderSortStatus: CustomSortStatus
};
<DataGrid
columns={columns}
rows={rows}
renderers={customRenderers}
sortColumns={sortColumns}
onSortColumnsChange={setSortColumns}
/>
Full control over header rendering:
import { type RenderHeaderCellProps } from 'react-data-grid';
function CustomHeader({ column, sortDirection }: RenderHeaderCellProps<Row>) {
return (
<div className="custom-header">
{column.name}
{sortDirection && (
<span className="sort-indicator">
{sortDirection === 'ASC' ? ' ↑' : ' ↓'}
</span>
)}
</div>
);
}
const columns: Column<Row>[] = [
{
key: 'name',
name: 'Name',
sortable: true,
renderHeaderCell: CustomHeader
}
];
Server-Side Sorting
Implement server-side sorting by fetching data when sort changes:
import { useEffect, useState } from 'react';
import { DataGrid, type SortColumn } from 'react-data-grid';
function MyGrid() {
const [rows, setRows] = useState([]);
const [sortColumns, setSortColumns] = useState<readonly SortColumn[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
async function fetchData() {
setLoading(true);
try {
const params = new URLSearchParams();
sortColumns.forEach((sort) => {
params.append('sort', `${sort.columnKey}:${sort.direction.toLowerCase()}`);
});
const response = await fetch(`/api/data?${params}`);
const data = await response.json();
setRows(data);
} finally {
setLoading(false);
}
}
fetchData();
}, [sortColumns]);
return (
<DataGrid
columns={columns}
rows={rows}
sortColumns={sortColumns}
onSortColumnsChange={setSortColumns}
/>
);
}
Advanced Sorting
Case-Insensitive String Sorting
const sortedRows = useMemo(() => {
if (sortColumns.length === 0) return rows;
return [...rows].sort((a, b) => {
for (const sort of sortColumns) {
const { columnKey, direction } = sort;
const aValue = a[columnKey as keyof Row];
const bValue = b[columnKey as keyof Row];
// Handle strings case-insensitively
const aCompare = typeof aValue === 'string' ? aValue.toLowerCase() : aValue;
const bCompare = typeof bValue === 'string' ? bValue.toLowerCase() : bValue;
if (aCompare < bCompare) {
return direction === 'ASC' ? -1 : 1;
}
if (aCompare > bCompare) {
return direction === 'ASC' ? 1 : -1;
}
}
return 0;
});
}, [rows, sortColumns]);
Null/Undefined Handling
const sortedRows = useMemo(() => {
if (sortColumns.length === 0) return rows;
return [...rows].sort((a, b) => {
for (const sort of sortColumns) {
const { columnKey, direction } = sort;
const aValue = a[columnKey as keyof Row];
const bValue = b[columnKey as keyof Row];
// Handle null/undefined - push to end
if (aValue == null && bValue == null) continue;
if (aValue == null) return 1;
if (bValue == null) return -1;
if (aValue < bValue) {
return direction === 'ASC' ? -1 : 1;
}
if (aValue > bValue) {
return direction === 'ASC' ? 1 : -1;
}
}
return 0;
});
}, [rows, sortColumns]);
TypeScript Types
interface SortColumn {
readonly columnKey: string;
readonly direction: SortDirection;
}
type SortDirection = 'ASC' | 'DESC';
interface DataGridProps<R, SR = unknown, K extends Key = Key> {
/** Array of sorted columns */
sortColumns?: Maybe<readonly SortColumn[]>;
/** Callback when sorting changes */
onSortColumnsChange?: Maybe<(sortColumns: SortColumn[]) => void>;
}
interface Column<TRow, TSummaryRow = unknown> {
/** Enable sorting for this column */
readonly sortable?: Maybe<boolean>;
/** Start with descending on first sort */
readonly sortDescendingFirst?: Maybe<boolean>;
}
interface RenderHeaderCellProps<TRow, TSummaryRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
sortDirection: SortDirection | undefined;
priority: number | undefined;
tabIndex: number;
}