Problem
Sorting an Expenses table by a column where every row is empty (e.g. Posted / Approved / Withdrawal ID) changes the row order, and ascending vs descending produce different orders.
Expected: sorting an all-empty column is a no-op — row order stays the same and is identical for both ascending and descending.
Root cause
The transaction sort adds a secondary tie-breaker that re-sorts tied rows by created, then transactionID — honoring the user's sortOrder:
|
if (primaryComparison !== 0) { |
|
return primaryComparison; |
|
} |
|
|
|
// If we have a tie in the primary comparison, we add a tie breaker on date and/or transactionID as a last resort to make the sort deterministic |
|
const createdComparison = compareValues(a.created, b.created, sortOrder, 'created', localeCompare); |
|
|
|
if (createdComparison !== 0) { |
|
return createdComparison; |
|
} |
|
|
|
return compareValues(a.transactionID, b.transactionID, sortOrder, 'transactionID', localeCompare); |
compareValues returns 0 when both values are empty, so for an all-empty column every primary comparison ties → the whole order is decided by that tie-breaker. Because it honors sortOrder, ascending orders the rows by date-ascending and descending by date-descending — hence the table reorders and flips between directions.
Why the report view is unaffected: its sort has no tie-breaker — it returns the primary compareValues directly and lets the stable Array.sort keep ties in place:
|
return compareValues( |
|
getTransactionSortValue(a, sortBy, report, policy, policyCategories), |
|
getTransactionSortValue(b, sortBy, report, policy, policyCategories), |
|
sortOrder, |
|
sortBy, |
|
localeCompare, |
|
true, |
|
); |
|
}); |
Introduced by: commit fac3dcefab9 "add tie breaker" in PR Expensify/App#77800 (Reimbursable/Billable columns) — added so low-cardinality (boolean) columns get a deterministic within-group order:
fac3dce
Proposed fix (FE)
Drop the sortOrder-dependent tie-breaker and return the primary comparison — the stable sort preserves ties, so an all-empty column becomes a true no-op, identical for asc/desc, and consistent with the report view.
Filed from this RCA comment by mukhrr on PR Expensify/App#93146.
Issue Owner
Current Issue Owner: @mallenexpensify
Problem
Sorting an Expenses table by a column where every row is empty (e.g. Posted / Approved / Withdrawal ID) changes the row order, and ascending vs descending produce different orders.
Expected: sorting an all-empty column is a no-op — row order stays the same and is identical for both ascending and descending.
Root cause
The transaction sort adds a secondary tie-breaker that re-sorts tied rows by
created, thentransactionID— honoring the user'ssortOrder:App/src/libs/SearchUIUtils.ts
Lines 3975 to 3986 in 241d30f
compareValuesreturns0when both values are empty, so for an all-empty column every primary comparison ties → the whole order is decided by that tie-breaker. Because it honorssortOrder, ascending orders the rows by date-ascending and descending by date-descending — hence the table reorders and flips between directions.Why the report view is unaffected: its sort has no tie-breaker — it returns the primary
compareValuesdirectly and lets the stableArray.sortkeep ties in place:App/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
Lines 380 to 388 in 241d30f
Introduced by: commit
fac3dcefab9"add tie breaker" in PRExpensify/App#77800(Reimbursable/Billable columns) — added so low-cardinality (boolean) columns get a deterministic within-group order:fac3dce
Proposed fix (FE)
Drop the
sortOrder-dependent tie-breaker and return the primary comparison — the stable sort preserves ties, so an all-empty column becomes a true no-op, identical for asc/desc, and consistent with the report view.Filed from this RCA comment by
mukhrron PRExpensify/App#93146.Issue Owner
Current Issue Owner: @mallenexpensify