Capítulo 45 de 54

Chapter 45: Sorting (React) Guide

Core Idea

Sorting state is an ordered array (multi-sort by construction), comparator functions are inferred by column data type unless overridden, and several table/column options — sortDescFirst, invertSorting, sortUndefined, enableSortingRemoval — exist specifically to handle the edge cases that trip up naive sort implementations (nullable columns, rank-style inverted scales, "must always have a sort applied" UIs).

Key Concepts

  • State: SortingState = Array<{ id: string, desc: boolean }> — an array, so multi-column sort is native, not a special case. Reactive read table.state.sorting, snapshot table.atoms.sorting.get(). Ownership: external atom (atoms.sorting, v9-recommended, e.g. for a query key) or classic state.sorting + onSortingChange, or initialState.sorting alone (never combine initialState with a controlled value for the same slice).
  • Setup: rowSortingFeature + sortedRowModel: createSortedRowModel() for client-side; manualSorting: true (row model omitted or explicitly disabled) for server-side — data is assumed pre-sorted. Same page-index auto-reset caveat as filtering/grouping: under manual sorting with the row model skipped, a sorting state change doesn't trigger the page-reset hook — reset by hand in the change handler.
  • 6 built-in sortFns: alphanumeric (mixed alphanumeric, case-insensitive, natural-sorts embedded numbers — slower, most accurate), alphanumericCaseSensitive, text (plain string compare, case-insensitive — faster, less accurate for embedded numbers), textCaseSensitive, datetime (for Date values), basic (a > b ? 1 : a < b ? -1 : 0 — fastest, least nuanced). Default sortFn: 'auto' infers one of alphanumeric/text/datetime from the column's data type — register whichever ones your columns actually rely on.
  • Custom sortFn signature: (rowA, rowB, columnId) => -1 | 0 | 1 — a plain comparator; it does not need to account for asc/desc itself, the row model handles direction. Register by name in sortFns for string references to type-check, or pass the function directly to skip registration.
  • constructSortFn({ sort, resolveDataValue? }) mirrors filtering's constructFilterFn: sort is the value-level comparator, resolveDataValue normalizes each side before comparison (honored by every built-in, since they're all built with this helper). Spread-and-override an existing one to derive a variant — e.g. a diacritic-insensitive alphanumeric that only swaps resolveDataValue, or a brand-new "sort by last word" comparator built the same way.
  • Direction defaults & overrides: first direction cycling through toggleSorting is ascending for strings, descending for numbers by default — override per-column or table-wide with sortDescFirst. Set it explicitly on columns with nullable values, since the table may fail to correctly infer string-vs-number when values are sometimes null/undefined.
  • invertSorting: true flips the result of a sort while the asc/desc UI toggle cycle stays normal — for "lower is better" scales like race rank or golf score, so "desc" still reads as "worst to best" in the UI's own vocabulary rather than requiring users to think backwards.
  • sortUndefined (column or table option; default 1) controls where undefined values land: 'first'/'last' push them to an end explicitly; 1 = lower priority (end when ascending); -1 = higher priority (start when ascending); false = no special handling, the comparator itself must deal with undefined.
  • enableSortingRemoval (default true) controls whether the toggle cycle includes a 'none' state: 'none' → 'asc' → 'desc' → 'none' → ... by default, or 'none' → 'asc' → 'desc' → 'asc' → ... when set false (once sorted, a column can never return to unsorted — though sorting a different column non-multi-sort still clears the previous one). Set false when the UI requires at least one column always sorted.
  • Multi-sorting is enabled by default via getToggleSortingHandler + Shift-click (Shift-clicking a header adds that column to the existing sort instead of replacing it); using column.toggleSorting(desc, multi) directly requires passing the multi-sort boolean yourself. enableMultiSort: false (per-column or table-wide) forces sort-by-just-this-column always. isMultiSortEvent swaps the trigger modifier (or, returning true unconditionally, makes every click a multi-sort). maxMultiSortColCount caps how many columns can be sorted at once. enableMultiRemove (default true) controls whether an individual column can be removed from an active multi-sort.
  • autoResetSorting: sorting is preserved across data changes by default (unlike page index/expansion, which reset). Set true to reset sorting whenever data's reference changes — responds only to data changes, not to sorting/filter/grouping changes themselves. Dangerous to combine with manual/server-side sorting: a server response normally replaces data, so enabling this can immediately wipe the sorting state that produced that very response.

Code Examples

// deriving a diacritic-insensitive alphanumeric sort by overriding only resolveDataValue
const stripDiacritics = (v: string) => v.normalize('NFD').replace(/\p{Diacritic}/gu, '')
const alphanumericIgnoreDiacritics = constructSortFn({
  ...sortFn_alphanumeric,
  resolveDataValue: (v) => stripDiacritics(sortFn_alphanumeric.resolveDataValue!(v)),
})

columnHelper.accessor('rank', { invertSorting: true, sortUndefined: 'last' })
  • What it demonstrates: deriving a sort variant by overriding just the normalization hook, and combining invertSorting/sortUndefined for a rank-style column.

Key Takeaways

  1. Set sortDescFirst explicitly on nullable columns — automatic string-vs-number inference can misfire when values are sometimes missing.
  2. invertSorting is for scales where a lower value is "better" (rank, golf score) — it changes the actual row order, not the toggle-cycle labels.
  3. enableSortingRemoval: false guarantees a column stays sorted once clicked — pick it deliberately for UIs that must never show an unsorted table.
  4. Don't combine autoResetSorting: true with manual server-side sorting unless you're certain a data refresh should also clear the sort that requested it.

Connects To

  • Column Filtering (React) Guide: constructFilterFn, the filtering-side sibling of constructSortFn.
  • Fuzzy Filtering (React) Guide: a sortFn that reads filterMeta instead of comparing raw values directly.
  • Row Pinning (React) Guide: the feature that runs immediately before sorting in the row-reorder pipeline.