Fix duplicate expense navigation arrows showing inverted state on first open#83753
Fix duplicate expense navigation arrows showing inverted state on first open#83753
Conversation
…st open When opening a duplicate expense from the Review Duplicates screen, MoneyRequestReportTransactionList's useEffect was racing with Review.tsx's onPreviewPressed, overwriting the active transaction IDs with the parent report's visual order. This caused the left/right navigation arrows to show inverted state on the first open. Skip setting active transaction IDs when the SEARCH_REPORT was opened from the duplicate review context (backTo ends with /duplicates/review), matching the existing isFromReviewDuplicates pattern in MoneyRequestHeader. Co-authored-by: Chavda Sachin <ChavdaSachin@users.noreply.github.com>
|
I created this PR from an upstream branch since I don't have push access to your fork. To take ownership of this branch and be able to push updates, run: Then you can close this PR and open a new one from your fork. |
|
@MelvinBot current PR approach is not good enough, I have two candidate approaches here, please analyze and do not make changes to the code yet. Option 1 - As Option 2 - In |
|
Thanks for the feedback Analysis of Option 1 — Remove
|
Review.tsx |
MoneyRequestReportTransactionList.tsx |
|
|---|---|---|
| Transaction set | Duplicate sibling transactions (from DUPLICATED_TRANSACTION violation data) |
All transactions on the report |
| Purpose | Navigate prev/next through duplicate siblings | Navigate prev/next through report expenses |
When a user taps a duplicate in Review.tsx, onPreviewPressed builds siblingTransactionIDsList from the current transaction + its duplicates (sourced from violation data at Review.tsx:54-65), then navigates to SEARCH_REPORT. If we removed this call, MoneyRequestReportTransactionList's useEffect would set the active IDs to the report's full transaction list — the prev/next arrows would then cycle through all report expenses instead of through the duplicate siblings, which is incorrect behavior.
Additionally, Review.tsx has a restore mechanism (Review.tsx:71-97) — it saves the pre-existing transactionIDsList on first click and restores it via useFocusEffect when navigating back. Removing the setActiveTransactionIDs call would also break this restoration logic.
Analysis of Option 2 — Sort siblingTransactionIDsList in Review.tsx with the same logic as MoneyRequestReportTransactionList.tsx
Verdict: This doesn't solve the problem because the transaction sets differ, not just the sort order.
Even if Review.tsx used the same compareValues() sort logic from MoneyRequestReportTransactionList.tsx, the two callers would still produce different ID arrays for three reasons:
-
Different transaction sets:
Review.tsxhas duplicate sibling IDs (e.g.,[txn_A, txn_B]— two duplicates), whileMoneyRequestReportTransactionListhas the full report's transaction IDs (e.g.,[txn_A, txn_B, txn_C, txn_D]). Since the arrays have different elements, the idempotency check insetActiveTransactionIDswould still detect a mismatch and overwrite. -
Grouping:
MoneyRequestReportTransactionListcan group transactions by category/tag (sorted alphabetically viasortGroupedTransactionsinReportLayoutUtils.ts:13-22), further reordering IDs.Review.tsxhas no grouping concept. -
User-configurable sort column:
MoneyRequestReportTransactionListlets users sort by merchant, category, tag, amount, or description — not just date. Even if you aligned the default date sort, a user who previously sorted by amount would still get a different order.
Why the current PR approach (guard in useEffect) is the right direction
The core issue is that these two callers serve different navigation contexts:
Review.tsx→ "navigate between duplicate siblings"MoneyRequestReportTransactionList→ "navigate between report expenses"
A guard that prevents one from overwriting the other is the architecturally correct solution. The backTo URL check in the current PR accurately detects the duplicate review context.
That said, if there are concerns about the fragility of URL string matching (backTo?.replaceAll(/\?.*/g, '').endsWith('/duplicates/review')), one alternative refinement could be using a more explicit signal — for example, passing a boolean flag through route params or using a context/ref that Review.tsx sets before navigation to indicate "I've already set the active IDs, don't overwrite."
|
@MelvinBot do you have any other ideas here? |
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
|
Here are some alternative ideas, beyond the two options we already analyzed and the current PR's URL guard approach: Idea 1 — Source-aware
|
|
I would prefer not to rely on the routes/ backTo |
This one makes the most sense to me. @mountiny should I prepare CP using this idea? |
|
Also could we add "help wanted" label back, in case someone has a better idea. |
|
I might have one from Claude |
|
@ChavdaSachin let me know what you think #83772 |
Explanation of Change
When opening a duplicate expense from the "Review duplicates" screen, the navigation arrows (left/right) showed inverted state on the first open but correct state on the second open. This was caused by a race condition between two callers of
setActiveTransactionIDs:Review.tsx'sonPreviewPressedsets the IDs to the duplicate-sorted orderMoneyRequestReportTransactionList'suseEffectfires whenvisualOrderTransactionIDsgets a new reference (due to navigation-triggered re-renders) and overwrites the IDs with the parent report's visual sort orderThis fix adds a guard to the
useEffectinMoneyRequestReportTransactionList.tsxto skip setting active transaction IDs when theSEARCH_REPORTwas opened from the duplicate review context (detected via thebackToroute param). This matches the existingisFromReviewDuplicatespattern already used inMoneyRequestHeader.tsx.Fixed Issues
$ #83751
PROPOSAL: #83751 (comment)
Tests
Offline tests
N/A - This change only affects navigation state during UI interaction and does not involve network requests.
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari