Reference solution
The findings below are ordered by priority.
Findings
1. Sorting mutates the parent-owned rows array
Location: src/sortable-table.tsx:20-26
Array.prototype.sort operates in place, so the memo changes a prop while rendering. That can reorder data observed by sibling components, violate React's immutable data assumptions, and make later updates difficult to reason about.
Copy before sorting, for example with toSorted or a spread followed by sort.
2. Sort controls are unavailable from the keyboard
Location: src/sortable-table.tsx:38-55
The click handler is attached directly to th, which is not an interactive control and does not receive native Enter or Space activation. Keyboard users cannot invoke the feature and focus is not exposed consistently.
Place a native button inside each header and attach the action to that button.
3. Text columns do not sort as text
Location: src/sortable-table.tsx:20-25
Every cell is coerced through Number. Ordinary names become NaN, so the comparator effectively treats them as equal and text columns fail to sort. The declared column kind is ignored.
Select a comparator from the active column metadata: numeric subtraction for numbers and localeCompare for strings, returning zero for equal values so stable order is retained.
4. Index keys attach row state to positions
Location: src/sortable-table.tsx:59-64
Sorting and insertion change each row's array index, so key={rowIndex} can make React reuse a row subtree for a different record. Any stateful cell can then display or edit the wrong row.
Use the row's documented stable id as the row key.
Reasonable non-findings
- Memoizing the sorted view is reasonable for a reusable table.
- The toggle expression correctly starts a new column ascending and flips an active ascending column to descending.
- Native table elements provide the appropriate structural semantics.