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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Closing over a state object lets a returned function remember context across calls without globals.
- 2Decorate-sort-undecorate turns any comparator into a stable sort by using original index as a tiebreaker.
- 3A robust comparator must decide null ordering and type handling explicitly rather than relying on default coercion.
Related explainers
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
javascript
import { useEffect, useRef, useState } from 'react'; export function useDelayedFlag(active, delay = 300) { const [visible, setVisible] = useState(false);
Delaying a loading spinner with a React hook
custom-hooks
debouncing
cleanup
Intermediate
8 steps
javascript
const SWIPE_THRESHOLD = 80; const MAX_TRANSLATE = 120; export function attachSwipeToDismiss(element, onDismiss) {
Building a swipe-to-dismiss gesture in JS
touch-events
gesture-detection
dom-manipulation
Intermediate
10 steps
python
from typing import Any, Sequence, Mapping def render_markdown_table(
Rendering an aligned Markdown table in Python
string formatting
data transformation
closures
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-stateful-table-sorter-in-javascript-explained-javascript-83ce/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.