javascript 39 lines · 7 steps

Building a stateful table sorter in JavaScript

A closure remembers the last sorted column so repeat clicks flip direction, using a stable comparator that handles nulls and mixed types.

Explained by highlit
1function makeTableSorter(rows) {
2 const state = { key: null, dir: 1 };
3 
4 const compareBy = (key) => (a, b) => {
5 const av = a[key];
6 const bv = b[key];
7 if (av == null && bv == null) return 0;
8 if (av == null) return 1;
9 if (bv == null) return -1;
10 if (typeof av === 'number' && typeof bv === 'number') {
11 return av - bv;
12 }
13 return String(av).localeCompare(String(bv), undefined, {
14 numeric: true,
15 sensitivity: 'base',
16 });
17 };
18 
19 const stableSort = (arr, cmp) =>
20 arr
21 .map((item, index) => ({ item, index }))
22 .sort((x, y) => cmp(x.item, y.item) || x.index - y.index)
23 .map(({ item }) => item);
24 
25 return function sortByColumn(key) {
26 if (state.key === key) {
27 state.dir *= -1;
28 } else {
29 state.key = key;
30 state.dir = 1;
31 }
32 
33 const base = compareBy(key);
34 const cmp = (a, b) => base(a, b) * state.dir;
35 
36 rows = stableSort(rows, cmp);
37 return { rows, sortedBy: key, direction: state.dir === 1 ? 'asc' : 'desc' };
38 };
39}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Closing over a state object lets a returned function remember context across calls without globals.
  2. 2Decorate-sort-undecorate turns any comparator into a stable sort by using original index as a tiebreaker.
  3. 3A robust comparator must decide null ordering and type handling explicitly rather than relying on default coercion.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a stateful table sorter in JavaScript — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code