Skip to content

[Due for payment 2026-04-01] [Due for payment 2026-03-30] Expense - Disabled tax rate appears twice when submitting expense from self DM #85652

Closed

Description

@lanitochka17

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.40-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: #78472
Email or phone of affected tester (no customers): sjdoisjndoisjndjnoisj@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:

Precondition:

  • Enable Taxes in workspace.
  1. Go to staging.new.expensify.com
  2. Go to self DM.
  3. Create an expense with 5% tax rate.
  4. Go to workspace settings > Taxes.
  5. Delete 5% tax rate.
  6. Go back to self DM.
  7. Click Submit it to someone.
  8. Select workspace chat.
  9. On confirm page, click Tax.

Expected Result:

Disabled tax rate will appear once.

Actual Result:

Disabled tax rate appears twice.

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7106240_1773840266466.6.mp4

View all open jobs on GitHub

Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

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

    @melvin-bot

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

  3. melvin-bot commented on Mar 18, 2026

    @melvin-bot

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

  4. github-actions commented on Mar 18, 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 Mar 18, 2026

    @OSBotify
    Contributor

    🔍 Investigation Summary

    Classification: Frontend bug
    Causing PR: #78472 - "Improve deleted tax behavior" by @huult (High confidence)
    Related Issues: #73033 (original issue this PR was fixing)

    Recommendation: REVERT

    The PR introduced a logic error in the duplicate detection code. While the intent was correct (show deleted tax rates as disabled), the implementation has a bug that causes the deleted tax rate to appear twice.

    Labels: Keeping DeployBlockerCash (frontend bug in App). The DeployBlocker label was not present on this issue.

    📋 Detailed Analysis

    Evidence

    PR #78472 was specifically designed to handle deleted tax rates by showing them as disabled options in the tax picker. The issue reports that when submitting an expense from self DM to workspace chat and clicking on Tax, the disabled (deleted) tax rate appears twice - which directly relates to the changes made in this PR.

    The PR:

    Verification

    The affected file is /home/runner/_work/App/App/src/components/TaxPicker.tsx

    Confirmed that PR #78472 modified this exact file and introduced the problematic code that is currently present in the codebase.

    Root Cause

    The bug is in lines 128-133 of TaxPicker.tsx:

    const updatedSections = deletedTaxOption
        ? sections.map((section) => ({
              ...section,
              data: [...section.data.map((item) => (item.code === deletedTaxOption.code ? {...item, isSelected: false} : item)), deletedTaxOption],
          }))
        : sections;

    The issue is on line 131: item.code === deletedTaxOption.code

    Looking at line 75-85, deletedTaxOption is created with:

    const deletedTaxOption = !hasTaxBeenDeleted
        ? null
        : {
              code: undefined,  // ← Set to undefined!
              text: taxValue ?? ,
              keyForList: taxCode ?? ,
              // ...
          };

    The deletedTaxOption.code is explicitly set to undefined, so the comparison item.code === deletedTaxOption.code will never match any existing items in the list. This means:

    1. The deleted tax option that may already be in the sections (from getTaxRatesSection) is NOT filtered out
    2. The deletedTaxOption is added to the end of the array
    3. Result: The deleted tax appears twice

    The comparison should use keyForList instead of code to properly identify and remove the duplicate before adding the new deleted option.

  6. MelvinBot commented on Mar 18, 2026

    @MelvinBot
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When a user creates an expense with a specific tax rate in their self DM, then deletes that tax rate from workspace settings, and subsequently tries to submit the expense to a workspace chat, clicking "Tax" on the confirm page shows the disabled tax rate twice in the picker list.

    What is the root cause of that problem?

    The duplication is caused by an interaction between two independent code paths in TaxPicker.tsx that both add the deleted tax to the list:

    Path 1 — selectedOptions in getTaxRatesSection:
    When submitting from self DM, the action is SUBMIT, which causes isMovingTransactionFromTrackExpense() to return true (src/libs/IOUUtils.ts:307-312). This makes getTaxRateTitle() fall through to the isMovingFromTrackExpense branch (src/libs/TransactionUtils/index.ts:2196-2198), which calls getTaxName() with shouldFallbackToValue=true. Since the tax is deleted (not found in policy), this returns transaction.taxValue (e.g., "5%") as a fallback (src/libs/TransactionUtils/index.ts:2155-2156). This non-empty selectedTaxRate creates a selectedOptions entry in getTaxRatesSection, which gets included in the sections as a disabled, selected item with code: undefined (since the selectedOptions object has no code property — src/components/TaxPicker.tsx:87-95).

    Path 2 — deletedTaxOption in TaxPicker:
    Since the tax code is absent from taxRates.taxes (removed by Onyx after successful deletion), hasTaxBeenDeleted is true (src/components/TaxPicker.tsx:72), and a deletedTaxOption is created with code: undefined and text: "5%" (src/components/TaxPicker.tsx:75-85).

    Why the dedup logic fails:
    The updatedSections code at line 128-133 tries to deselect the matching item via item.code === deletedTaxOption.code. Since both items have code: undefined, the match succeeds — it sets isSelected: false on the Path 1 item but does not remove it from the list. Then deletedTaxOption is appended as a second entry. The user sees two disabled "5%" entries.

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

    In src/components/TaxPicker.tsx, when hasTaxBeenDeleted is true, the updatedSections logic should filter out the matching item from the section data (rather than just deselecting it) before appending deletedTaxOption. Change the map at line 131 from:

    item.code === deletedTaxOption.code ? {...item, isSelected: false} : item
    

    to a .filter() that removes items where item.code === deletedTaxOption.code, then append deletedTaxOption. This ensures only one entry for the deleted tax appears.

    What alternative solutions did you explore?

    1. Clear selectedOptions when hasTaxBeenDeleted is true — Pass empty selectedOptions to getTaxRatesSection when the tax has been deleted. This would prevent Path 1 from adding the entry, but it changes the behavior of section building in a way that could have other side effects.
    2. Fix in getTaxRateTitle — Return empty string when the tax is deleted, even in the isMovingFromTrackExpense path. However, the fallback to taxValue might be intentionally needed for display purposes on the confirm page itself, so removing it could break other UI.

    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. huult commented on Mar 18, 2026

    @huult
    Contributor

    checking...

  8. 9 remaining items

  9. added
    Awaiting PaymentAuto-added when associated PR is deployed to production
    and removed on Mar 23, 2026
  10. changed the title [-]Expense - Disabled tax rate appears twice when submitting expense from self DM[/-] [+][Due for payment 2026-03-30] Expense - Disabled tax rate appears twice when submitting expense from self DM[/+] on Mar 23, 2026
  11. melvin-bot commented on Mar 23, 2026

    @melvin-bot

    Reviewing label has been removed, please complete the "BugZero Checklist".

  12. melvin-bot commented on Mar 23, 2026

    @melvin-bot

    The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.41-4 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:

    If no regressions arise, payment will be issued on 2026-03-30. 🎊

    For reference, here are some details about the assignees on this issue:

    • @huult requires payment through NewDot Manual Requests
    • @thesahindia requires payment through NewDot Manual Requests
  13. melvin-bot commented on Mar 23, 2026

    @melvin-bot

    @huult / @thesahindia The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]

  14. melvin-bot commented on Mar 23, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  15. changed the title [-][Due for payment 2026-03-30] Expense - Disabled tax rate appears twice when submitting expense from self DM[/-] [+][Due for payment 2026-04-01] [Due for payment 2026-03-30] Expense - Disabled tax rate appears twice when submitting expense from self DM[/+] on Mar 25, 2026
  16. melvin-bot commented on Mar 25, 2026

    @melvin-bot

    The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.43-3 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:

    If no regressions arise, payment will be issued on 2026-04-01. 🎊

    For reference, here are some details about the assignees on this issue:

    • @huult requires payment through NewDot Manual Requests
    • @thesahindia requires payment through NewDot Manual Requests
  17. melvin-bot commented on Mar 25, 2026

    @melvin-bot

    @huult / @thesahindia @mallenexpensify The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]

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

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.EngineeringWeeklyKSv2

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions