Repository navigation
Spend - Only "Current View" option is visible when selecting group row before expanding it. #99089
Description
Activity
- addedDailyKSv2KSv2BugSomething is broken. Auto assigns a BugZero manager.Something is broken. Auto assigns a BugZero manager.
on Aug 20, 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/a18b2844330b3c56b5a636901072a4db9cc946cc528234b8d6eb8612f77713dfHi! I'd like to take this issue. I’ll investigate the group selection and bulk action menu behavior and open a PR with the fix. Could you please assign it to me?
📣 @asmaatefomran! 📣
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:- Make sure you've read and understood the contributing guidelines.
- 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>Proposal
What is the root cause of that problem?
Category groups load their child transactions lazily — a group's
transactionsarray stays empty until the group is expanded once (expansion fires asearch()for the group'stransactionsQueryJSONsub-snapshot). So when you tick a group-header checkbox on a group that has never been expanded, the selection code receives an empty transactions array.In that empty case,
toggletakes its "empty report" branch and stores a single placeholder entry keyed by the group key. That placeholder has every capability flag set tofalse(canHold,canChangeReport, …) and carries no underlying transaction.useSearchBulkActionsgates Edit Multiple / Hold / Move to Report / Duplicate / Delete on those per-transaction flags, so they are all skipped — only the Export option survives, which collapses to its single "Current View" item.Once you expand the group, the sub-snapshot populates
group.transactions, so re-selecting registers real per-transaction entries and the full option set appears — exactly the behavior described in the report.Investigation details (file:line evidence, confidence: High)
- Group-header checkbox passes the group's (empty, when collapsed) transactions to
toggle:GroupHeader.tsx:195 toggleempty-transactions branch stores one placeholder under the group key:SearchWriteActionsProvider.tsx:467- Placeholder entry has all capability flags
falseand no.transaction:selectionBuilders.ts:110 - Bulk-action options gated on those flags; only Export is pushed unconditionally:
useSearchBulkActions.ts:2356(Hold ~L2445, Move ~L2569, Delete/Duplicate ~L2583) - Group children are fetched only on expand:
GroupChildrenContent.tsx:92 group.transactionsis enriched from the sub-snapshot only after it exists:useSearchSnapshot.ts:259- Reconcile converts a group-key selection into real per-row entries once children load:
SearchWriteActionsProvider.tsx:170
Why Export still works while the group is collapsed: Export builds a whole-group query filter from the group key (
useSearchBulkActions.ts:183), so it doesn't need loaded children — unlike the other actions.What changes do you think we should make in order to solve the problem?
When a group-header checkbox is selected for a lazy group whose children haven't loaded yet, trigger the same fetch that expansion performs, so the existing reconcile logic converts the placeholder into real per-transaction entries (which then enable the full option set — no changes needed in
useSearchBulkActions).Concretely, in
toggle's empty-transactions branch atSearchWriteActionsProvider.tsx:467: distinguish a genuinely empty report from a not-yet-fetched lazy group usingitem.transactionsQueryJSON(a lazy group has one; an empty report does not — the same predicateGroupHeaderuses forisEmpty). If it's a lazy group with no loaded children, still store the placeholder (so the checkbox shows selected immediately) and dispatch the samesearch({queryJSON: item.transactionsQueryJSON, …})thatGroupChildrenContent.refreshTransactionsruns on expand. Once the sub-snapshot arrives,useSearchSnapshotenrichesgroup.transactionsanduseReconcileSelectionWithDatarewrites the selection into real per-transaction entries, enabling Edit / Hold / Move / Delete / Duplicate automatically.What alternative solutions did you explore? (Optional)
Make
useSearchBulkActionsgroup-aware for Hold / Move / Delete / Duplicate so options can be computed synchronously for a still-collapsed group from group-level aggregates. This is a substantially larger change and duplicates capability logic, so the fetch-on-select approach is preferred. Note the recommended fix is asynchronous — options appear once the sub-snapshot loads (near-instant online).Possibly related (not a duplicate): #95369 — "Spend-Hybrid-Tap on checkbox does not select the group" (closed) — same group-checkbox area.
Next Steps for Contributor+ team:
To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/99089)to create a draft PR.
To refine:@MelvinBot <your feedback>
To reject: Explain why you are rejecting Melvin's proposal.- Group-header checkbox passes the group's (empty, when collapsed) transactions to
- addedExternalAdded to denote the issue can be worked on by a contributorAdded to denote the issue can be worked on by a contributor
on Aug 20, 2026 Triggered auto assignment to Contributor-plus team member for initial proposal review - @truph01 (
External)Job added to Upwork: https://www.upwork.com/jobs/~022090475639752015558
Contributor details
Your Expensify account email: asmaatef6789@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~0121f2fae71a421cf3✅ Contributor details stored successfully. Thank you for contributing to Expensify!
Proposal
Please re-state the problem that we are trying to solve in this issue.
In Spend / Search with grouped rows (e.g. grouped by Category), selecting the checkbox on a collapsed group header only displays the "Current View" (under Export) option in the bulk actions menu. The expected bulk actions (Hold, Move to report, Delete, Duplicate, Edit multiple) do not appear until the user expands the group and selects it again.
What is the root cause of that problem?
-
Lazy Loading of Group Transactions:
Grouped search results load child transactions lazily. A group'stransactionsarray remains empty until the group is expanded, which triggerssearch({queryJSON: groupItem.transactionsQueryJSON, ...})in GroupChildrenContent.tsx:92-99. -
Placeholder Selection with Disabled Capabilities:
When the user checks the group header checkbox while the group is collapsed, GroupHeader.tsx:195-197 passes an emptygroupItem.transactionsarray ([]) totoggle().
In SearchWriteActionsProvider.tsx:471-486,toggle()enters thecurrentTransactions.length === 0branch and creates a placeholder entry viamapEmptyReportToSelectedEntry(item).
This placeholder entry has all action capabilities set tofalse(canHold: false,canChangeReport: false,canDelete: false,canDuplicate: false, etc.) because no underlying transactions are loaded yet. -
Bulk Actions Menu Filtering:
useSearchBulkActions.ts gates each bulk action option on these capability flags. Because all flags on the placeholder arefalse, all actions (Hold, Move, Delete, Duplicate, etc.) are filtered out. Only theExportaction is included, which collapses to the single "Current View" menu item. -
Missing Fetch on Selection:
The group's transactions sub-snapshot is only fetched upon expanding the group (isExpanded), and is never triggered when selecting the group while collapsed.
What changes do you think we should make in order to solve the problem?
- In SearchWriteActionsProvider.tsx, inside the
togglefunction's empty transactions branch (currentTransactions.length === 0), check if the selected item is a lazy group with atransactionsQueryJSON:- If
item.transactionsQueryJSONexists and the group is being selected, dispatchsearch({ queryJSON: item.transactionsQueryJSON, offset: 0, shouldCalculateTotals: false, isLoading: false, isOffline }). - Store the initial group placeholder entry so the checkbox reflects the selected state immediately in the UI.
- If
if (currentTransactions.length === 0 && item.keyForList) { const reportKey = item.keyForList; if (item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { return selectedTransactions; } if (selectedTransactions[reportKey]?.isSelected) { const reducedSelectedTransactions: SelectedTransactions = {...selectedTransactions}; delete reducedSelectedTransactions[reportKey]; return reducedSelectedTransactions; } // Trigger fetching the group's child transactions if it's an unexpanded lazy group if (item.transactionsQueryJSON) { search({ queryJSON: item.transactionsQueryJSON, searchKey: undefined, offset: 0, shouldCalculateTotals: false, isLoading: false, isOffline, }); } const [, emptyReportSelection] = mapEmptyReportToSelectedEntry(item); return {...selectedTransactions, [reportKey]: emptyReportSelection}; }
- Once the sub-snapshot arrives from the server/cache, the existing
useReconcileSelectionWithDatahook in SearchWriteActionsProvider.tsx:170-228 automatically maps the group selection into real per-transaction entries (mapTransactionItemToSelectedEntry) with active capability flags, enabling the full set of bulk actions (Hold, Move to report, Delete, Duplicate, Edit multiple) without requiring manual group expansion.
What specific scenarios should we cover in automated tests to prevent regression?
Add unit tests in
tests/unit/SearchWriteActionsProviderTest.tsx:- Selecting a collapsed group with
transactionsQueryJSON:- Verify that
search()is called withitem.transactionsQueryJSON. - Verify that the group key is added to
selectedTransactions.
- Verify that
- Reconciling after sub-snapshot arrives:
- Verify that once group transactions are loaded in Onyx,
useReconcileSelectionWithDatareplaces the group placeholder with populated per-transaction entries containing active action capabilities (canHold,canChangeReport,canDelete, etc.).
- Verify that once group transactions are loaded in Onyx,
- Deselecting a collapsed group:
- Verify that toggling the checkbox again correctly removes the group from
selectedTransactionswithout redundant searches.
- Verify that toggling the checkbox again correctly removes the group from
What alternative solutions did you explore? (Optional)
- Synchronously computing capabilities from group-level metadata: Calculating bulk actions based on group summaries without fetching child transactions. However, individual transaction permissions (e.g., whether specific expenses are held, policy permissions, report statuses) cannot be reliably determined without child transactions, and would duplicate permission logic across
useSearchBulkActions. Fetching on selection leverages the existing reconciliation pipeline cleanly.
Contributor Details
- Your Expensify account email: talibusama1234@gmail.com
- Upwork Profile Link: https://www.upwork.com/freelancers/~0169f347c5f3562928
-
@MelvinBot Your main solution could be problematic when there are many grouped transactions. For example, if there are 100 groups, clicking the header toggle would trigger 100 API calls.
I don't think this is the best approach from a performance perspective.
Reacted by melvin-bot18 remaining items
Actual Result:
Only "Current View" option is visible on dropdown menu after selecting expenses group before expanding it. "Edit Multiple", "Hold", "Move to Report", "Duplicate Expenses", and "Delete" options are not visible until expenses group is expanded for the first time.This is because a collapsed group carries no transaction data — the server sends only category / count / total / currency, and the children are fetched on expand. Hold / Move / Delete / Duplicate / Edit Multiple are gated on per-transaction flags, so they can't be enabled before that data exists.
I want to hear the thoughts from internal/ @Expensify/design about it.
🎀 👀 🎀 C+ reviewed
Triggered auto assignment to @jasperhuangg, see https://stackoverflow.com/c/expensify/questions/7972 for more details.
Hmm @JmillsExpensify @JS00001 @trjExpensify what would be the expected behavior here for selecting groups in Spend?
Only "Current View" option is visible on dropdown menu after selecting expenses group before expanding it. "Edit Multiple", "Hold", "Move to Report", "Duplicate Expenses", and "Delete" options are not visible until expenses group is expanded for the first time.
I dont remember ever implementing these for groups. This feels expected, but maybe we want to implement it as a new feature?
Right, we don't know enough about the expenses nested within the group to give you expense level action options until you expand one of them, right? IIRC, that was a big no-no for performance in the initial implementation?
What you do get without expanding the group is the option to export the groups and the transaction data, but we do that group export server-side.
Yeah, thats correct
@jasperhuangg Whoops! This issue is 2 days overdue. Let's get this updated quick!
@jasperhuangg 6 days overdue. This is scarier than being forced to listen to Vogon poetry!
@JS00001 So what should we do next in this issue?
I think we should probably close this as expected for the time being. I dont think we want to start returning all of the results up front, we should stick with the current implementation
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.
Version Number: 9.4.57-0
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #98716
Email or phone of affected tester (no customers): ibellicotest+2766@gmail.com
Issue reported by: Applause Internal Team
Bug source: Pull Request QA execution
Device used: Motorola MotoG60 / Android 12 (Hybrid app) - Windows 11 / Chrome
App Component: Search
Action Performed:
Prerequisite: Account has at least one workspace.
Expected Result:
"Edit Multiple", "Hold", "Move to Report", "Duplicate Expenses", and "Delete" options should be always visible on dropdown menu after selecting a group row.
Actual Result:
Only "Current View" option is visible on dropdown menu after selecting expenses group before expanding it. "Edit Multiple", "Hold", "Move to Report", "Duplicate Expenses", and "Delete" options are not visible until expenses group is expanded for the first time.
Workaround:
Unknown
Platforms:
Screenshots/Videos
Bug7238354_1787239473785.Grouped_Options.mp4
View all open jobs on GitHub
Upwork Automation - Do Not Edit
Issue Owner
Current Issue Owner: @jasperhuangg