You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Version Number: 9.3.84-0 Reproducible in staging?: Yes Reproducible in production?: No If this was caught during regression testing, add the test name, ID and link from BrowserStack:#91710 Email or phone of affected tester (no customers):applausetester+05272026_1@applause.expensifail.com Issue reported by: Applause Internal Team Bug source: Pull Request QA execution Device used: Windows 11/ Chrome App Component: Other
Action Performed:
Open the staging.new.expensify.com website.
Create new report
Add two expenses to the report and force the second one of them to have a violation
Go to Spend -> Reports
Open the created report
Change sort to Total
Expected Result:
Verify you have the default sort (Date/ASC) set. RBR pre-sort is gone.
Actual Result:
Default sort for Date is set to DESC. RBR pre-sort is still at the top
User clicks Total → isActive=false → sort becomes Total/DESC
User clicks Date again → isActive=false → sort becomes Date/DESC (bug: should be ASC)
What changes do you think we should make in order to solve the problem?
Modify the nextSortOrder logic in SortableHeaderText.tsx:60 so that when switching to a non-active column, the default sort order is ASC instead of DESC. The toggle behavior (clicking an already-active column flips the order) should remain unchanged.
What alternative solutions did you explore? (Optional)
An alternative would be to handle this in the onSortPress callback of MoneyRequestReportTransactionList.tsx:741 by overriding the sort order when the column changes. However, fixing it in SortableHeaderText is cleaner since it's the shared component responsible for computing the next sort order and the same issue would affect any consumer. Note that this change would also affect the Search table headers that use the same component — this is likely the correct behavior there too (defaulting to ASC when first clicking a column), but should be verified during QA.
Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, @MelvinBot <your feedback> to refine this analysis, or explain why you are rejecting Melvin's proposal.
But the direction a header click produces is not decided here. It is decided by the shared SortableHeaderText, which anchors the first click of any inactive column to DESC:
So after Date → Amount → Date the table lands on Date/DESC, isDefaultSort stays false, and the flagged expense stops floating to the top.
I measured the emitted order rather than inferring it. Rendering the real MoneyRequestReportTableHeader in Jest with Amount active and pressing Date emits ['date', 'desc'], and driving PR #91710's steps 5 → 7 → 8 through the same onSortPress body fails on current main with:
That made the shared anchor wrong for this view immediately, but with no visible effect until the isDefaultSort gate gave the lost default a consequence.
Why the listed steps do not reproduce it
@rojiphil the OP's step list stops at "Change sort to Total". PR #91710's test plan has an eighth step — "Switch back to Date/ASC and verify RBR-flagged item is back on the top" — and the Applause export dropped it. Following the six listed steps, nothing is wrong.
The second reason it is easy to miss: the Date sort value is created, which is written date-only.
Two expenses created on the same day compare equal, and Array.prototype.sort is stable, so ASC and DESC render the same rows in the same order — only the arrow flips. Expenses on different days separate cleanly.
On the second sentence of the Actual Result ("RBR pre-sort is still at the top"): with Amount active, isDefaultSort is false and rbrTransactionIDs is null — my run asserts that — so nothing pins the flagged row there. If it is still on top, it also wins the amount comparison. That is not a second defect, and a tie-break in the comparator cannot change the sort direction the title is about.
Nothing catches it: SortableHeaderText and SortableTableHeader have no test anywhere in tests/, and the only unit test for this component replicates one effect in isolation instead of rendering the header.
What changes do you think we should make in order to solve the problem?
First, a correction to my own earlier proposal. I suggested a defaultSortOrder prop on SortableHeaderText. That is wrong: the prop is per table, not per column, so passing ASC would also flip the first click of Amount, Merchant, Description, Category and Tag in this same table. That is a behaviour change well beyond this bug.
The default belongs to the view, so the translation belongs in the view. Hoist the pair this file already spells out twice into one constant, and reuse it in the handler:
Only the third hunk changes behaviour. I hoist rather than add a fourth DATE/ASC literal because the drift between the two places that spell the default out and the one that did not is the bug.
Regression safety:
Search is untouched. Same column, opposite default, and Search writes the emitted order into the URL query, so changing the shared header would re-rank saved and shared search links:
setSortConfig has exactly one call site, the handler above.
Toggling the active Date column is unaffected: selectedSortBy !== sortBy is false, so repeat clicks still flip ASC↔DESC. Switching to any non-default column still lands on DESC.
No offline or optimistic path is involved: sortConfig is local useState, with no API call and no failureData.
The grouped layout derives from sortedTransactions, so it inherits the corrected order with no extra change.
One limitation I want to be straight about: a user who deliberately sets Date/DESC, switches to Amount and comes back to Date gets ASC, not the DESC they last chose. The view has one default and this makes it reachable in one click; a second click still gives DESC.
Tests, all of which I ran:
Steps 5 → 7 → 8: open, click Amount, click Date, expect date/asc and isDefaultSort true. Fails on current main with the output above, passes with the fix.
Date → Date still toggles DESC then ASC. Passes on main and with the fix.
Merchant and Amount still land on DESC. Passes on main and with the fix.
MoneyRequestReportTransactionListActiveTransactionIDsTest, MoneyRequestReportViewTest, MoneyRequestReportGroupHeaderTest — 15 tests, green with the fix applied.
I did not render the whole transaction list in Jest. Test 1 drives the real MoneyRequestReportTableHeader → SortableTableHeader → SortableHeaderText chain and reproduces the onSortPress body verbatim.
What alternative solutions did you explore? (Optional)
Flip the ternary to ASC. Rejected: Search's Date default is DESC, so this makes the shared header wrong for its other consumer, and Search's order is encoded in the URL.
A defaultSortOrder prop, my earlier proposal. Rejected above: per table rather than per column, so it changes five other columns in this table.
A deterministic tie-break in the comparator. Worth doing on its own, but it only makes equal rows stable; it cannot make Date land on ASC, so it does not address the reported default sort.
Please re-state the problem that we are trying to solve in this issue.
In the money request report view (Spend → Reports → open a report), the initial default sort is Date/ASC so that RBR-flagged transactions are surfaced at the top. After clicking the Total column header to change sort, then clicking the Date column header again, Date sorts in DESC order instead of returning to the expected Date/ASC default. This contradicts step 8 of the test plan from the linked PR #91710 ("Switch back to Date/ASC and verify RBR-flagged item is back on the top") because clicking Date no longer restores the table's documented default ordering.
What is the root cause of that problem?
The READ side — MoneyRequestReportTransactionList — initialises sortConfig with {sortBy: DATE, sortOrder: ASC} and treats that combination as the "default sort" guard that triggers the RBR pre-sort path. The WRITE side — clicks coming up from SortableHeaderText — computes the next order with logic that does not know about this table's ASC default:
Line 60 reads const nextSortOrder = isActive && sortOrder === DESC ? ASC : DESC. When the user clicks a column that is not currently active (isActive is false because sortBy === 'total' after step 6, and Date is being re-clicked in step 7), the expression short-circuits to the falsy branch and returns DESC. The press handler in MoneyRequestReportTransactionList then writes that order straight into state without translating it to the table's per-column default:
The full chain is therefore: user clicks Total → SortableHeaderText emits ('total', DESC) → state becomes {sortBy: 'total', sortOrder: 'DESC'} (correct behaviour for non-default columns) → user clicks Date → SortableHeaderText sees isActive=false for Date and emits ('date', DESC) → state becomes {sortBy: 'date', sortOrder: 'DESC'}. Because isDefaultSort on line 291 only fires when sortOrder === ASC, the RBR pre-sort path in rbrTransactionIDs (lines 297-311) is also bypassed, so the RBR-flagged transaction no longer floats to the top either. The behaviour in SortableHeaderText is the right global default for the main Search screen, which uses DESC as its defaultSortOrder in SearchUIUtils (line 646), but it is wrong for this specific table whose default is Date/ASC. The mismatch was latent before PR #91710 — the new test plan added the Total → Date round-trip step, which is why staging surfaces it but production (which still ran the old test plan) does not.
What changes do you think we should make in order to solve the problem?
Fix it locally inside MoneyRequestReportTransactionList's onSortPress handler so the global SortableHeaderText keeps its existing behaviour for Search (which legitimately defaults to DESC). When the user clicks a column that is not the currently active sort column (i.e. selectedSortBy !== sortBy), use the table's per-column default: ASC when selectedSortBy === CONST.SEARCH.TABLE_COLUMNS.DATE, otherwise the incoming selectedSortOrder (DESC). When the user clicks the already-active column, honour selectedSortOrder so toggling Date between ASC and DESC still works.
Concretely, inside the existing onSortPress callback after the isSortableColumnName guard, compute const nextOrder = selectedSortBy !== sortBy && selectedSortBy === CONST.SEARCH.TABLE_COLUMNS.DATE ? CONST.SEARCH.SORT_ORDER.ASC : selectedSortOrder and pass that as sortOrder into setSortConfig. This restores the original Date/ASC default on switch-back, which in turn re-arms isDefaultSort on line 291 and re-enables the RBR pre-sort that PR #91710 wanted to validate. The change is scoped to a single handler and a single column, does not modify any shared helper or other Search consumers, and preserves the column-toggle behaviour for repeated clicks on Date (DESC ↔ ASC) because the selectedSortBy !== sortBy check is false on a re-click of the active column.
What alternative solutions did you explore? (Optional)
Adding a per-column default-order map to SortableHeaderText (so the global component computes ASC for Date and DESC for everything else) was considered but rejected — it would change behaviour for the main Search screen and every other consumer of SortableTableHeader, where DESC is the intentional default (defaultSortOrder = DESC in SearchUIUtils.ts). Keeping the fix local to the one table that has an ASC default avoids any cross-screen regressions.
⚠️@yusufdeveloper2903 Thanks for your proposal. Please update it to follow the proposal template, as proposals are only reviewed if they follow that format (note the mandatory sections).
⚠️@MobileMage Your proposal is a duplicate of an already existing proposal and has been automatically withdrawn to prevent spam. Please review the existing proposals before submitting a new one.
Please re-state the problem that we are trying to solve in this issue.
When sorting reports or data tables in the app (e.g. Spend -> Reports), clicking on a non-active column header defaults to sorting descending (DESC) instead of ascending (ASC). The expected behavior across standard tables is that clicking an unselected column should sort it in ascending order first, and subsequent clicks on the same active column toggle between ascending and descending.
What is the root cause of that problem?
In src/components/SortableHeaderText.tsx, line 60:
When an inactive column header is clicked, isActive is false. Because of the isActive && ... expression, it evaluates to false and automatically falls into the falsy ternary branch, returning CONST.SORT_ORDER.DESC.
What changes do you think we should make in order to solve the problem?
We should separate the condition so that if the column is NOT currently active (!isActive), the next sort order must default to ASC. Only if the column IS active (isActive) should we toggle between ASC and DESC:
What alternative solutions did you explore? (Optional)
None, fixing the ternary logic directly in SortableHeaderText.tsx is the cleanest, most performant, and root-cause fix without affecting any parent component states.
Unit Test / Regression Test Plan
In tests/unit/SortableHeaderTextTest.tsx:
Render SortableHeaderText with isActive={false} and sortOrder={CONST.SORT_ORDER.DESC}.
Press the header and verify onPress is called with CONST.SORT_ORDER.ASC.
Render with isActive={true} and sortOrder={CONST.SORT_ORDER.ASC}.
Press and verify onPress is called with CONST.SORT_ORDER.DESC.
Render with isActive={true} and sortOrder={CONST.SORT_ORDER.DESC}.
Press and verify onPress is called with CONST.SORT_ORDER.ASC.
📣 @b4fpj6jrzf-boop! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:
Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
Format:
Contributor details
Your Expensify account email: <REPLACE EMAIL HERE>
Upwork Profile Link: <REPLACE LINK HERE>
⚠️@b4fpj6jrzf-boop Thanks for your interest. To be considered for this job, please post a proposal following the proposal template (note the mandatory sections). Comments claiming the job without one are not reviewed.
⚠️@b4fpj6jrzf-boop Thanks for your interest. To be considered for this job, please post a proposal following the proposal template (note the mandatory sections). Comments claiming the job without one are not reviewed.
⚠️@b4fpj6jrzf-boop Thanks for your interest. To be considered for this job, please post a proposal following the proposal template (note the mandatory sections). Comments claiming the job without one are not reviewed.
Please re-state the problem that we are trying to solve in this issue.
In Spend / Reports tables, clicking an unselected column header (such as switching from Date to Total and back to Date) always sorts in descending (DESC) order instead of restoring the column default (ASC), and when transactions are grouped by Category or Tag, changing the sort order does not reorder the groups.
What is the root cause of that problem?
In src/components/Search/SortableHeaderText.tsx, clicking an inactive column (!isActive) always computes nextSortOrder = CONST.SEARCH.SORT_ORDER.DESC. useMoneyRequestReportSortedTransactions.ts stores that emitted DESC order directly even though the table initializes Date sort as ASC.
In src/libs/ReportLayoutUtils.ts, groupTransactionsByCategory and groupTransactionsByTag sort transactions first but then sort the resulting groups alphabetically by group key, which overrides the transaction sort order when each group contains a single transaction.
What changes do you think we should make in order to solve the problem?
In src/components/MoneyRequestReportView/useMoneyRequestReportSortedTransactions.ts (and SortableHeaderText.tsx via an optional defaultSortOrder prop), default newly selected columns in the report table to CONST.SEARCH.SORT_ORDER.ASC on first click and toggle between ASC and DESC when clicking the already-active column.
In src/libs/ReportLayoutUtils.ts, when an explicit sortBy / sortOrder is active on the report table, order the groups by their first (already sorted) transaction rather than re-sorting the groups alphabetically.
What alternative solutions did you explore? (Optional)
Changing SortableHeaderText globally for all Search views could alter main Search page defaults where Date DESC is expected, so scoping the default ASC behavior via a prop passed by MoneyRequestReportTransactionList avoids regressions in global Search.
Activity
applause-bot commentedon May 28, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/297f145c1eeb20e9d3e55802cae6333624c7b62935f10115ecdb0c62624c9de0
MelvinBot commentedon May 28, 2026
Proposal
What is the root cause of that problem?
The sort toggle logic in
SortableHeaderText.tsx:60always defaults toDESCwhen clicking a column that isn't currently active:When
isActiveisfalse(i.e., switching to a different column), the ternary short-circuits toDESCregardless. So the flow is:MoneyRequestReportTransactionList.tsx:285-288)isActive=false→ sort becomes Total/DESCisActive=false→ sort becomes Date/DESC (bug: should be ASC)What changes do you think we should make in order to solve the problem?
Modify the
nextSortOrderlogic inSortableHeaderText.tsx:60so that when switching to a non-active column, the default sort order isASCinstead ofDESC. The toggle behavior (clicking an already-active column flips the order) should remain unchanged.What alternative solutions did you explore? (Optional)
An alternative would be to handle this in the
onSortPresscallback ofMoneyRequestReportTransactionList.tsx:741by overriding the sort order when the column changes. However, fixing it inSortableHeaderTextis cleaner since it's the shared component responsible for computing the next sort order and the same issue would affect any consumer. Note that this change would also affect the Search table headers that use the same component — this is likely the correct behavior there too (defaulting to ASC when first clicking a column), but should be verified during QA.Next Steps for Contributor+ team: Reply with
@MelvinBot implement thisto create a draft PR,@MelvinBot <your feedback>to refine this analysis, or explain why you are rejecting Melvin's proposal.yusufdeveloper2903 commentedon May 28, 2026
🚨 Edited by proposal-police: This proposal was edited at 2026-08-26 09:34:11 UTC.
Proposal
What is the root cause of that problem?
The report transaction table declares its own default sort as Date/ASC, and gates the RBR pre-sort on exactly that pair:
App/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
Lines 432 to 438 in 9e84742
The RBR set is
nullfor any other pair, which is what switches violation-first ordering off:App/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
Lines 463 to 466 in 9e84742
But the direction a header click produces is not decided here. It is decided by the shared
SortableHeaderText, which anchors the first click of any inactive column toDESC:App/src/components/Search/SortableHeaderText.tsx
Lines 79 to 84 in 9e84742
SortableTableHeaderforwards that order verbatim, and this table'sonSortPresswrites it into state without translating it to the view's own default:App/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
Lines 931 to 936 in 9e84742
So after Date → Amount → Date the table lands on
Date/DESC,isDefaultSortstays false, and the flagged expense stops floating to the top.I measured the emitted order rather than inferring it. Rendering the real
MoneyRequestReportTableHeaderin Jest with Amount active and pressing Date emits['date', 'desc'], and driving PR #91710's steps 5 → 7 → 8 through the sameonSortPressbody fails on current main with:When it became wrong
The
DESCanchor is correct for the component's other consumer: Search's own Date sort isDESC.App/src/libs/SearchUIUtils.ts
Line 3306 in 9e84742
The two defaults diverged on 2025-03-24, when this table's initial order was changed from
DESCtoASCin #58903:App/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
Lines 78 to 82 in 228e7ab
That made the shared anchor wrong for this view immediately, but with no visible effect until the
isDefaultSortgate gave the lost default a consequence.Why the listed steps do not reproduce it
@rojiphil the OP's step list stops at "Change sort to Total". PR #91710's test plan has an eighth step — "Switch back to Date/ASC and verify RBR-flagged item is back on the top" — and the Applause export dropped it. Following the six listed steps, nothing is wrong.
The second reason it is easy to miss: the Date sort value is
created, which is written date-only.App/src/libs/actions/IOU/MoneyRequest.ts
Line 345 in 9e84742
App/src/libs/ReportUtils.ts
Lines 14103 to 14105 in 9e84742
Two expenses created on the same day compare equal, and
Array.prototype.sortis stable, so ASC and DESC render the same rows in the same order — only the arrow flips. Expenses on different days separate cleanly.On the second sentence of the Actual Result ("RBR pre-sort is still at the top"): with Amount active,
isDefaultSortis false andrbrTransactionIDsisnull— my run asserts that — so nothing pins the flagged row there. If it is still on top, it also wins the amount comparison. That is not a second defect, and a tie-break in the comparator cannot change the sort direction the title is about.Nothing catches it:
SortableHeaderTextandSortableTableHeaderhave no test anywhere intests/, and the only unit test for this component replicates one effect in isolation instead of rendering the header.App/tests/unit/MoneyRequestReportTransactionListActiveTransactionIDsTest.tsx
Lines 36 to 40 in 9e84742
What changes do you think we should make in order to solve the problem?
First, a correction to my own earlier proposal. I suggested a
defaultSortOrderprop onSortableHeaderText. That is wrong: the prop is per table, not per column, so passingASCwould also flip the first click of Amount, Merchant, Description, Category and Tag in this same table. That is a behaviour change well beyond this bug.The default belongs to the view, so the translation belongs in the view. Hoist the pair this file already spells out twice into one constant, and reuse it in the handler:
Only the third hunk changes behaviour. I hoist rather than add a fourth
DATE/ASCliteral because the drift between the two places that spell the default out and the one that did not is the bug.Regression safety:
App/src/components/Search/index.tsx
Lines 975 to 986 in 9e84742
setSortConfighas exactly one call site, the handler above.selectedSortBy !== sortByis false, so repeat clicks still flip ASC↔DESC. Switching to any non-default column still lands on DESC.sortConfigis localuseState, with no API call and nofailureData.sortedTransactions, so it inherits the corrected order with no extra change.One limitation I want to be straight about: a user who deliberately sets Date/DESC, switches to Amount and comes back to Date gets ASC, not the DESC they last chose. The view has one default and this makes it reachable in one click; a second click still gives DESC.
Tests, all of which I ran:
date/ascandisDefaultSorttrue. Fails on current main with the output above, passes with the fix.MoneyRequestReportTransactionListActiveTransactionIDsTest,MoneyRequestReportViewTest,MoneyRequestReportGroupHeaderTest— 15 tests, green with the fix applied.I did not render the whole transaction list in Jest. Test 1 drives the real
MoneyRequestReportTableHeader→SortableTableHeader→SortableHeaderTextchain and reproduces theonSortPressbody verbatim.What alternative solutions did you explore? (Optional)
ASC. Rejected: Search's Date default isDESC, so this makes the shared header wrong for its other consumer, and Search's order is encoded in the URL.defaultSortOrderprop, my earlier proposal. Rejected above: per table rather than per column, so it changes five other columns in this table.MobileMage commentedon May 28, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
In the money request report view (Spend → Reports → open a report), the initial default sort is Date/ASC so that RBR-flagged transactions are surfaced at the top. After clicking the Total column header to change sort, then clicking the Date column header again, Date sorts in DESC order instead of returning to the expected Date/ASC default. This contradicts step 8 of the test plan from the linked PR #91710 ("Switch back to Date/ASC and verify RBR-flagged item is back on the top") because clicking Date no longer restores the table's documented default ordering.
What is the root cause of that problem?
The READ side —
MoneyRequestReportTransactionList— initialisessortConfigwith{sortBy: DATE, sortOrder: ASC}and treats that combination as the "default sort" guard that triggers the RBR pre-sort path. The WRITE side — clicks coming up fromSortableHeaderText— computes the next order with logic that does not know about this table's ASC default:App/src/components/Search/SortableHeaderText.tsx
Lines 60 to 65 in 139b2c8
Line 60 reads
const nextSortOrder = isActive && sortOrder === DESC ? ASC : DESC. When the user clicks a column that is not currently active (isActiveis false becausesortBy === 'total'after step 6, and Date is being re-clicked in step 7), the expression short-circuits to the falsy branch and returnsDESC. The press handler inMoneyRequestReportTransactionListthen writes that order straight into state without translating it to the table's per-column default:App/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
Lines 741 to 746 in 139b2c8
The full chain is therefore: user clicks Total →
SortableHeaderTextemits('total', DESC)→ state becomes{sortBy: 'total', sortOrder: 'DESC'}(correct behaviour for non-default columns) → user clicks Date →SortableHeaderTextseesisActive=falsefor Date and emits('date', DESC)→ state becomes{sortBy: 'date', sortOrder: 'DESC'}. BecauseisDefaultSorton line 291 only fires whensortOrder === ASC, the RBR pre-sort path inrbrTransactionIDs(lines 297-311) is also bypassed, so the RBR-flagged transaction no longer floats to the top either. The behaviour inSortableHeaderTextis the right global default for the main Search screen, which usesDESCas itsdefaultSortOrderinSearchUIUtils(line 646), but it is wrong for this specific table whose default is Date/ASC. The mismatch was latent before PR #91710 — the new test plan added the Total → Date round-trip step, which is why staging surfaces it but production (which still ran the old test plan) does not.What changes do you think we should make in order to solve the problem?
Fix it locally inside
MoneyRequestReportTransactionList'sonSortPresshandler so the globalSortableHeaderTextkeeps its existing behaviour for Search (which legitimately defaults to DESC). When the user clicks a column that is not the currently active sort column (i.e.selectedSortBy !== sortBy), use the table's per-column default: ASC whenselectedSortBy === CONST.SEARCH.TABLE_COLUMNS.DATE, otherwise the incomingselectedSortOrder(DESC). When the user clicks the already-active column, honourselectedSortOrderso toggling Date between ASC and DESC still works.App/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
Lines 741 to 746 in 139b2c8
Concretely, inside the existing
onSortPresscallback after theisSortableColumnNameguard, computeconst nextOrder = selectedSortBy !== sortBy && selectedSortBy === CONST.SEARCH.TABLE_COLUMNS.DATE ? CONST.SEARCH.SORT_ORDER.ASC : selectedSortOrderand pass that assortOrderintosetSortConfig. This restores the original Date/ASC default on switch-back, which in turn re-armsisDefaultSorton line 291 and re-enables the RBR pre-sort that PR #91710 wanted to validate. The change is scoped to a single handler and a single column, does not modify any shared helper or other Search consumers, and preserves the column-toggle behaviour for repeated clicks on Date (DESC ↔ ASC) because theselectedSortBy !== sortBycheck is false on a re-click of the active column.What alternative solutions did you explore? (Optional)
Adding a per-column default-order map to
SortableHeaderText(so the global component computes ASC for Date and DESC for everything else) was considered but rejected — it would change behaviour for the main Search screen and every other consumer ofSortableTableHeader, where DESC is the intentional default (defaultSortOrder = DESCinSearchUIUtils.ts). Keeping the fix local to the one table that has an ASC default avoids any cross-screen regressions.melvin-bot commentedon May 28, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @rojiphil (
External)[-]Spend - Default sort is descending[/-][+][$250] Spend - Default sort is descending[/+]melvin-bot commentedon May 28, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022059919919895211074
github-actions commentedon May 28, 2026
github-actions commentedon May 28, 2026
156 remaining items
b4fpj6jrzf-boop commentedon Oct 17, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
When sorting reports or data tables in the app (e.g. Spend -> Reports), clicking on a non-active column header defaults to sorting descending (
DESC) instead of ascending (ASC). The expected behavior across standard tables is that clicking an unselected column should sort it in ascending order first, and subsequent clicks on the same active column toggle between ascending and descending.What is the root cause of that problem?
In
src/components/SortableHeaderText.tsx, line 60:When an inactive column header is clicked,
isActiveisfalse. Because of theisActive && ...expression, it evaluates tofalseand automatically falls into the falsy ternary branch, returningCONST.SORT_ORDER.DESC.What changes do you think we should make in order to solve the problem?
We should separate the condition so that if the column is NOT currently active (
!isActive), the next sort order must default toASC. Only if the column IS active (isActive) should we toggle betweenASCandDESC:In
src/components/SortableHeaderText.tsx:Or cleanly written using an early return/ternary:
What alternative solutions did you explore? (Optional)
None, fixing the ternary logic directly in
SortableHeaderText.tsxis the cleanest, most performant, and root-cause fix without affecting any parent component states.Unit Test / Regression Test Plan
In
tests/unit/SortableHeaderTextTest.tsx:SortableHeaderTextwithisActive={false}andsortOrder={CONST.SORT_ORDER.DESC}.onPressis called withCONST.SORT_ORDER.ASC.isActive={true}andsortOrder={CONST.SORT_ORDER.ASC}.onPressis called withCONST.SORT_ORDER.DESC.isActive={true}andsortOrder={CONST.SORT_ORDER.DESC}.onPressis called withCONST.SORT_ORDER.ASC.melvin-bot commentedon Oct 17, 2026
📣 @b4fpj6jrzf-boop! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:
Format:
b4fpj6jrzf-boop commentedon Oct 17, 2026
melvin-bot commentedon Oct 17, 2026
github-actions commentedon Oct 17, 2026
b4fpj6jrzf-boop commentedon Oct 17, 2026
melvin-bot commentedon Oct 17, 2026
github-actions commentedon Oct 17, 2026
b4fpj6jrzf-boop commentedon Oct 17, 2026
melvin-bot commentedon Oct 17, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
github-actions commentedon Oct 17, 2026
open5588 commentedon Oct 19, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
In Spend / Reports tables, clicking an unselected column header (such as switching from Date to Total and back to Date) always sorts in descending (
DESC) order instead of restoring the column default (ASC), and when transactions are grouped by Category or Tag, changing the sort order does not reorder the groups.What is the root cause of that problem?
src/components/Search/SortableHeaderText.tsx, clicking an inactive column (!isActive) always computesnextSortOrder = CONST.SEARCH.SORT_ORDER.DESC.useMoneyRequestReportSortedTransactions.tsstores that emittedDESCorder directly even though the table initializesDatesort asASC.src/libs/ReportLayoutUtils.ts,groupTransactionsByCategoryandgroupTransactionsByTagsort transactions first but then sort the resulting groups alphabetically by group key, which overrides the transaction sort order when each group contains a single transaction.What changes do you think we should make in order to solve the problem?
src/components/MoneyRequestReportView/useMoneyRequestReportSortedTransactions.ts(andSortableHeaderText.tsxvia an optionaldefaultSortOrderprop), default newly selected columns in the report table toCONST.SEARCH.SORT_ORDER.ASCon first click and toggle betweenASCandDESCwhen clicking the already-active column.src/libs/ReportLayoutUtils.ts, when an explicitsortBy/sortOrderis active on the report table, order the groups by their first (already sorted) transaction rather than re-sorting the groups alphabetically.What alternative solutions did you explore? (Optional)
Changing
SortableHeaderTextglobally for all Search views could alter main Search page defaults whereDate DESCis expected, so scoping the defaultASCbehavior via a prop passed byMoneyRequestReportTransactionListavoids regressions in global Search.Contributor details
Your Expensify account email: open5588@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~0159c00273dd3bd644
melvin-bot commentedon Oct 19, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!