docs: add Excel-like sorting guide for null/undefined handling#6085
docs: add Excel-like sorting guide for null/undefined handling#6085jiji-hoon96 wants to merge 1 commit intoTanStack:mainfrom
Conversation
Add comprehensive guide for Excel-like sorting behavior Include complete working example with TypeScript Reference community solution from issue TanStack#6061 Update API docs to clarify sortUndefined limitations Refs: TanStack#6061
WalkthroughDocumentation updates: one API doc clarified the scope of sortUndefined (undefined-only, not null) with a link to a new guide. A new guide was added demonstrating an Excel-like sorting approach using a custom sorting function to place null/undefined values last across sort directions. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as Table UI
participant Core as Table Core
participant SortFn as excelLikeSortingFn
User->>UI: Click column header to sort
UI->>Core: Request sorted row model
Core->>SortFn: Compare a.value vs b.value
alt Either value is null or undefined
note over SortFn: Treat null/undefined as "empty"
SortFn-->>Core: Return ordering so empties are last
else Both values non-empty
SortFn-->>Core: Return standard comparison result
end
Core-->>UI: Sorted rows (empties last)
UI-->>User: Render updated table
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related issues
Poem
✨ Finishing Touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
docs/guide/excel-like-sorting.md (2)
56-73: Reframe global config sectionIf you adopt normalization +
sortUndefined: 'last', a global custom sortingFn is unnecessary. Suggest noting that Step 3 is optional only when you choose the comparator approach (asc-only behavior).
192-201: Tiny copy edit and spacingAdd a blank line before “Credits” for consistent section spacing.
- `cell` function controls display of empty values - ## Creditsdocs/api/features/sorting.md (1)
142-143: Good clarification; consider adding a quick tipThe note is accurate and the link path looks correct. Consider adding a one-liner tip: “Normalize null→undefined in
accessorFnand setsortUndefined: 'last'to keep empty values last for both directions.”
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
docs/api/features/sorting.md(1 hunks)docs/guide/excel-like-sorting.md(1 hunks)
🧰 Additional context used
🪛 LanguageTool
docs/guide/excel-like-sorting.md
[grammar] ~197-~197: There might be a mistake here.
Context: ...zes undefined to null - cell function controls display of empty values ## Credits Th...
(QB_NEW_EN)
| ```tsx | ||
| const excelLikeSortingFn = (rowA, rowB, columnId) => { | ||
| const a = rowA.getValue(columnId); | ||
| const b = rowB.getValue(columnId); | ||
|
|
||
| // Check for empty values (null, undefined) | ||
| const aEmpty = a == null; | ||
| const bEmpty = b == null; | ||
|
|
||
| // If both are empty, they're equal | ||
| if (aEmpty && bEmpty) return 0; | ||
|
|
||
| // Empty values always go to bottom | ||
| if (aEmpty) return 1; | ||
| if (bEmpty) return -1; | ||
|
|
||
| // Normal comparison for non-empty values | ||
| return a < b ? -1 : a > b ? 1 : 0; | ||
| }; | ||
| ``` |
There was a problem hiding this comment.
Custom sortingFn cannot keep nulls “always last” in both directions
Table desc sorting inverts the comparator result, so this function will place null/undefined at the top when sorting desc. It contradicts the stated “always at the bottom regardless of sort direction.” Use sortUndefined: 'last' and normalize null→undefined instead of relying on a comparator to enforce direction-agnostic placement.
Apply this diff to mark the function as optional and clarify intent:
-### Step 1: Create a Custom Sorting Function
+### Step 1 (Optional): Custom Sorting Function
@@
- // Empty values always go to bottom
+ // NOTE: This keeps empties last for ascending only.
+ // For "always last" in both directions, prefer normalizing to `undefined`
+ // and using `sortUndefined: 'last'` (see next step).📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ```tsx | |
| const excelLikeSortingFn = (rowA, rowB, columnId) => { | |
| const a = rowA.getValue(columnId); | |
| const b = rowB.getValue(columnId); | |
| // Check for empty values (null, undefined) | |
| const aEmpty = a == null; | |
| const bEmpty = b == null; | |
| // If both are empty, they're equal | |
| if (aEmpty && bEmpty) return 0; | |
| // Empty values always go to bottom | |
| if (aEmpty) return 1; | |
| if (bEmpty) return -1; | |
| // Normal comparison for non-empty values | |
| return a < b ? -1 : a > b ? 1 : 0; | |
| }; | |
| ``` | |
| ### Step 1 (Optional): Custom Sorting Function | |
🤖 Prompt for AI Agents
In docs/guide/excel-like-sorting.md around lines 17 to 36, the provided
comparator attempts to force null/undefined always last but table descending
sort inverts comparator results so it fails; update the docs to mark the custom
sortingFn as optional, remove the claim that the comparator alone will keep
nulls last in both directions, and instead show/describe normalizing
null→undefined and using the built-in option sortUndefined: 'last' for
direction-agnostic placement; keep the comparator example only as an optional
fallback for custom value comparison (not for controlling undefined ordering).
| const columns = [ | ||
| { | ||
| id: 'price', | ||
| accessorFn: row => row.price ?? null, | ||
| header: 'Price', | ||
| cell: ({ getValue }) => { | ||
| const value = getValue(); | ||
| return value == null ? '-' : `$${value}`; | ||
| }, | ||
| sortingFn: excelLikeSortingFn, | ||
| sortUndefined: 'last' | ||
| } | ||
| ]; |
There was a problem hiding this comment.
Normalize null→undefined and drop sortingFn
To keep empty values at the bottom for both asc/desc, normalize null to undefined and rely on sortUndefined: 'last'. Current code does the opposite (undefined→null), making sortUndefined ineffective for those values.
const columns = [
{
id: 'price',
- accessorFn: row => row.price ?? null,
+ accessorFn: row => (row.price == null ? undefined : row.price),
header: 'Price',
cell: ({ getValue }) => {
const value = getValue();
- return value == null ? '-' : `$${value}`;
+ return value == null ? '-' : `$${value}`;
},
- sortingFn: excelLikeSortingFn,
sortUndefined: 'last'
}
];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const columns = [ | |
| { | |
| id: 'price', | |
| accessorFn: row => row.price ?? null, | |
| header: 'Price', | |
| cell: ({ getValue }) => { | |
| const value = getValue(); | |
| return value == null ? '-' : `$${value}`; | |
| }, | |
| sortingFn: excelLikeSortingFn, | |
| sortUndefined: 'last' | |
| } | |
| ]; | |
| const columns = [ | |
| { | |
| id: 'price', | |
| accessorFn: row => (row.price == null ? undefined : row.price), | |
| header: 'Price', | |
| cell: ({ getValue }) => { | |
| const value = getValue(); | |
| return value == null ? '-' : `$${value}`; | |
| }, | |
| sortUndefined: 'last' | |
| } | |
| ]; |
🤖 Prompt for AI Agents
In docs/guide/excel-like-sorting.md around lines 41 to 53, the accessor
currently converts undefined to null and also sets a custom sortingFn, which
prevents the built-in sortUndefined: 'last' behavior from working; change the
accessorFn to normalize empty values to undefined (e.g., return row.price ??
undefined) and remove the sortingFn property so the column relies on
sortUndefined: 'last' to keep empty values at the bottom for both ascending and
descending sorts.
| function ExcelSortingTable() { | ||
| // Excel-like sorting function | ||
| const excelLikeSortingFn = (rowA, rowB, columnId) => { | ||
| const a = rowA.getValue(columnId); | ||
| const b = rowB.getValue(columnId); | ||
|
|
||
| if (a == null && b == null) return 0; | ||
| if (a == null) return 1; | ||
| if (b == null) return -1; | ||
|
|
||
| return a < b ? -1 : a > b ? 1 : 0; | ||
| }; | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Remove the per-example sortingFn; rely on normalization + sortUndefined
The inline excelLikeSortingFn still fails “always last” for nulls on desc. Simplify by removing it.
function ExcelSortingTable() {
- // Excel-like sorting function
- const excelLikeSortingFn = (rowA, rowB, columnId) => {
- const a = rowA.getValue(columnId);
- const b = rowB.getValue(columnId);
-
- if (a == null && b == null) return 0;
- if (a == null) return 1;
- if (b == null) return -1;
-
- return a < b ? -1 : a > b ? 1 : 0;
- };
+ // Normalize null to undefined and use sortUndefined: 'last' per column.Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In docs/guide/excel-like-sorting.md around lines 95 to 107, the inline
excelLikeSortingFn forces nulls to behave incorrectly (they remain "always last"
on descending sorts); remove this per-example sortingFn entirely and rely on the
existing value normalization plus the table/sort utility's sortUndefined
handling instead—delete the custom comparator and update the example to
demonstrate sorting using normalized values and sortUndefined configuration so
null/undefined are handled correctly by the shared sort logic.
| id: 'price', | ||
| accessorFn: row => row.price ?? null, | ||
| header: 'Price', | ||
| cell: ({ getValue }) => { | ||
| const value = getValue(); | ||
| return value == null ? '-' : `$${value}`; | ||
| }, | ||
| sortingFn: excelLikeSortingFn, | ||
| sortUndefined: 'last' | ||
| }, | ||
| { | ||
| id: 'stock', | ||
| accessorFn: row => row.stock ?? null, | ||
| header: 'Stock', | ||
| cell: ({ getValue }) => { | ||
| const value = getValue(); | ||
| return value == null ? 'N/A' : value; | ||
| }, | ||
| sortingFn: excelLikeSortingFn, | ||
| sortUndefined: 'last' | ||
| } |
There was a problem hiding this comment.
Fix column definitions to achieve “always last”
Normalize null→undefined and remove sortingFn from both columns. Keep sortUndefined: 'last'.
{
id: 'price',
- accessorFn: row => row.price ?? null,
+ accessorFn: row => (row.price == null ? undefined : row.price),
header: 'Price',
cell: ({ getValue }) => {
const value = getValue();
return value == null ? '-' : `$${value}`;
},
- sortingFn: excelLikeSortingFn,
sortUndefined: 'last'
},
{
id: 'stock',
- accessorFn: row => row.stock ?? null,
+ accessorFn: row => (row.stock == null ? undefined : row.stock),
header: 'Stock',
cell: ({ getValue }) => {
const value = getValue();
return value == null ? 'N/A' : value;
},
- sortingFn: excelLikeSortingFn,
sortUndefined: 'last'
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| id: 'price', | |
| accessorFn: row => row.price ?? null, | |
| header: 'Price', | |
| cell: ({ getValue }) => { | |
| const value = getValue(); | |
| return value == null ? '-' : `$${value}`; | |
| }, | |
| sortingFn: excelLikeSortingFn, | |
| sortUndefined: 'last' | |
| }, | |
| { | |
| id: 'stock', | |
| accessorFn: row => row.stock ?? null, | |
| header: 'Stock', | |
| cell: ({ getValue }) => { | |
| const value = getValue(); | |
| return value == null ? 'N/A' : value; | |
| }, | |
| sortingFn: excelLikeSortingFn, | |
| sortUndefined: 'last' | |
| } | |
| { | |
| id: 'price', | |
| accessorFn: row => (row.price == null ? undefined : row.price), | |
| header: 'Price', | |
| cell: ({ getValue }) => { | |
| const value = getValue(); | |
| return value == null ? '-' : `$${value}`; | |
| }, | |
| sortUndefined: 'last' | |
| }, | |
| { | |
| id: 'stock', | |
| accessorFn: row => (row.stock == null ? undefined : row.stock), | |
| header: 'Stock', | |
| cell: ({ getValue }) => { | |
| const value = getValue(); | |
| return value == null ? 'N/A' : value; | |
| }, | |
| sortUndefined: 'last' | |
| } |
🤖 Prompt for AI Agents
In docs/guide/excel-like-sorting.md around lines 115 to 135, the price and stock
column defs currently normalize missing values to null and explicitly set
sortingFn; update both columns to normalize missing values to undefined (e.g.
change accessorFn from row => row.price ?? null to row => row.price ?? undefined
and similarly for stock) and remove the sortingFn property from both column
objects while keeping sortUndefined: 'last'.
Add comprehensive guide for Excel-like sorting behavior Include complete working example with TypeScript
Reference community solution from issue #6061
Update API docs to clarify sortUndefined limitations
Refs: #6061
Summary by CodeRabbit