Skip to content

Expense - “Amount · Non-reimbursable” does not appear immediately after deleting expense #91058

Closed

Description

@jponikarchuk

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Version Number: 9.3.77-0
Reproducible in staging?: Yes
Reproducible in production?: N/A - new feature, doesn't exist in prod
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #90411
Email or phone of affected tester (no customers): sdsdjoijdoisnjodis@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Mac 26.2 / Chrome
App Component: Money Requests

Action Performed:

  1. Go to staging.new.expensify.com
  2. Go to workspace chat.
  3. Create two non-reimbursable expenses.
  4. Open expense report.
  5. Open both expenses.
  6. Delete one of the expenses.

Expected Result:

“Amount · Non-reimbursable” will appear on the remaining expense immediately.

Actual Result:

Reimbursable and Non-reimbursable row appear briefly, then it shows “Amount · Non-reimbursable”.

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome / Safari

Screenshots/Videos

Video.mp4

View all open jobs on GitHub

Activity

  1. added
    DeployBlockerCashThis issue or pull request should block deployment
    BugSomething is broken. Auto assigns a BugZero manager.
    on May 19, 2026
  2. melvin-bot commented on May 19, 2026

    @melvin-bot

    You have been assigned to this deploy blocker because you recently merged this PR: #90411

  3. melvin-bot commented on May 19, 2026

    @melvin-bot

    💬 A slack conversation has been started in #expensify-open-source

  4. github-actions commented on May 19, 2026

    @github-actions
    Contributor

    👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:

    1. Identify the pull request that introduced this issue and revert it.
    2. Find someone who can quickly fix the issue.
    3. Fix the issue yourself.
  5. OSBotify commented on May 19, 2026

    @OSBotify
    Contributor

    Investigation Summary

    Classification: Frontend bug
    Causing PR: #90411 - "Hide redundant Reimbursable rows; show '. Non-reimbursable' on Amount" by @mukhrr (High confidence)
    Related Issues: #89548

    Recommendation: REVERT

    PR #90411 introduced the logic for hiding the breakdown rows when a single non-reimbursable expense is present. The bug is a UI flash caused by a race condition in the new logic during expense deletion — reverting this PR will restore the previous stable behavior.

    Assigned: @mukhrr (PR author), @hoangzinh and @rlinoz (approving reviewers)
    Labels: DeployBlockerCash kept (frontend App PR is the cause). No label changes made.

    Detailed Analysis

    Evidence

    Verification

    Root Cause

    PR #90411 added isSingleNonReimbursableExpense logic in MoneyReportView.tsx:

    const isSingleNonReimbursableExpense =
      isSingleTransactionReport(report, transactions) &&
      transactions.at(0)?.reimbursable === false;
    const shouldShowBreakdown =
      !isSingleNonReimbursableExpense &&
      (!!nonReimbursableSpend || !!billableTotal || (!!taxTotal && isTaxEnabled));

    isSingleTransactionReport checks transactions.length === 1 using data from useReportTransactions, which reads from the Onyx TRANSACTION collection. When the user deletes one of two non-reimbursable expenses, the Onyx state updates asynchronously: the transactions array (from the collection) drops to length 1 before the report-level nonReimbursableSpend field is updated. During this brief window, isSingleNonReimbursableExpense is false (because both values haven't settled yet — e.g., the remaining transaction may still have reimbursable as undefined transiently), causing shouldShowBreakdown to evaluate to true and both "Reimbursable" and "Non-reimbursable" rows to flash before the correct state is reached.

  6. MelvinBot commented on May 19, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    When a transaction is deleted, the optimistic update marks it with pendingAction: DELETE but does not remove it from Onyx — the actual removal only happens in successData after the API responds. Meanwhile, the report's total and nonReimbursableTotal are updated optimistically in the same batch.

    This creates a brief intermediate state where useReportTransactions still returns 2 transactions (it doesn't filter out pendingAction: DELETE), so isSingleTransactionReport returns false. That makes isSingleNonReimbursableExpense false, which in turn makes shouldShowBreakdown true — causing the Reimbursable/Non-reimbursable breakdown rows to flash before the transaction is fully removed and the view settles.

    What changes do you think we should make in order to solve the problem?

    In MoneyReportView.tsx, filter out transactions with pendingAction === DELETE before passing them to isSingleTransactionReport and using the count for isSingleNonReimbursableExpense. This keeps the transaction-count logic in sync with the already-optimistically-updated report totals, preventing the intermediate state from producing a visible flicker.

    This is a local filter in the component rather than a change to useReportTransactions globally, which avoids unintended side effects elsewhere (e.g., pending-delete transactions still need to render with strikethrough styling in list views).

    What alternative solutions did you explore? (Optional)

    Filtering pendingAction: DELETE transactions globally in useReportTransactions was considered but rejected — other consumers of that hook rely on seeing pending-delete transactions to render them with appropriate visual indicators (strikethrough, opacity). A component-local filter is the minimal, safe change.

    Supporting evidence

    Data flow during the flicker:

    1. User deletes one of two non-reimbursable expenses
    2. Optimistic update: transaction gets pendingAction: DELETE (stays in Onyx); report totals are reduced
    3. useReportTransactions returns 2 transactions → isSingleTransactionReport → false → shouldShowBreakdown → true → breakdown rows appear
    4. API success: transaction set to null in Onyx → useReportTransactions returns 1 → isSingleNonReimbursableExpense → true → breakdown rows disappear → "Amount · Non-reimbursable" shown

    Key code locations:


    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.

  7. hoangzinh commented on May 19, 2026

    @hoangzinh
    Contributor

    We're trying to fix this DB here #91073

  8. roryabraham commented on May 19, 2026

    @roryabraham
    Contributor
  9. m-natarajan commented on May 19, 2026

    @m-natarajan

    Not repro, Chrome, Xiaomi Redmi Note 10 / Android 12v9.3.77-3. Note: on both platforms, after delete expense, user navigated to report details briefly (with 2 expenses) and only after - to the last expense details (Waiting for validation in iOS)

    Screenrecorder-2026-05-19-23-29-43-395.mp4
    bandicam.2026-05-19.23-23-38-898.mp4
  10. added and removed
    DeployBlockerCashThis issue or pull request should block deployment
    on May 19, 2026
  11. hoangzinh commented on May 20, 2026

    @hoangzinh
    Contributor

    We can close this issue. No payment required

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2EngineeringReviewingHas a PR in review

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions