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
{{ message }}
Repository navigation
[Due for payment 2026-10-26] [$175] [Sentry: APP-KT0] Search bulk actions crash reading undefined search results data #103775
currentSearchResults?.data is undefined while selected reports exist, and the code indexes into it without optional chaining on data. Likely fix: use currentSearchResults?.data?.[...].
Reproduction
Unknown. See the events linked from the Sentry issue.
Related
Possible sibling (not verified): APP-C89, same 'reading report_' pattern on /search/view/:hash
In useSearchBulkActions, the selectedMergeReports computation indexes currentSearchResults?.data[...]. The ?. only guards currentSearchResults. When currentSearchResults exists but data is undefined (for example while the snapshot is loading or being cleared, with selected reports still in state), undefined[...] throws. Every other access to this object in the file uses currentSearchResults?.data?.[...], so this is the only unguarded one.
When data is missing, the lookup gives undefined, so selectedMergeReports ends up shorter than selectedReports and the merge option is not shown. This is the correct behaviour.
Add a unit test in the useSearchBulkActions tests, "does not crash when search results have no data": render the hook with selected reports and currentSearchResults set to {search: {...}} with no data. Expect no throw and no merge option.
Grep the file for any other .data[ access without ?.. I found only this one.
What alternative solutions did you explore? (Optional)
Skip the merge computation when currentSearchResults?.data is undefined. This is more code for the same result.
Fall back to searchResults (the last non-empty ref). This changes behaviour, and the code elsewhere deliberately uses the current results.
The merge-reports check in useSearchBulkActions reads currentSearchResults?.data[...]. The ?. guards only currentSearchResults, not data. This is the only unguarded data access in the file.
data can be missing at runtime. When a search starts for a new hash, its optimistic update creates the snapshot with only search and errors. data stays missing until the response arrives, or for good if the request fails. Report selection lives in a separate provider, so selected reports can still exist on that render. The lookup then reads from undefined and throws.
TypeScript does not catch this because SearchResults.data is typed as required.
Every other access in the same file uses data?.[...], for example L753 and L2012. The hook also has an explicit !currentSearchResults?.data check at L662-L667.
I didn't reproduce this in the app. The root cause comes from the stack trace and the code path.
What changes do you think we should make in order to solve the problem?
In useSearchBulkActions.ts, add optional chaining on data in the selectedMergeReports lookup, so it reads currentSearchResults?.data?.[...].
When data is missing, every lookup returns undefined and gets filtered out. The lengths then don't match, so the hook hides the Merge option until results load. That is the right behavior.
Add a unit test. Given selected reports and currentSearchResults with search but no data, the hook should not throw and should not offer Merge.
What alternative solutions did you explore? (Optional)
Make data optional in the SearchResults type so TypeScript flags unguarded accesses like this one. This prevents repeats, such as the possible sibling APP-C89. But it touches many call sites, so it fits a separate cleanup better than this fix.
Note: The existing proposal from haiquywork describes the same root cause and fix.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/103775) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
Please re-state the problem that we are trying to solve in this issue.
[Sentry: APP-KT0] Unhandled TypeError: Cannot read properties of undefined (reading 'report_<reportID>') occurring in useSearchBulkActions on the /search page.
What is the root cause of that problem?
In src/hooks/useSearchBulkActions.ts (around lines 2921-2925):
The optional chaining operator ?. only guards currentSearchResults, but fails to guard .data.
When search results are loading, being refreshed, or right after an optimistic clear where currentSearchResults has the structure {search: {...}} without data, currentSearchResults?.data evaluates to undefined. If reports are currently selected in state, indexing into undefined[${ONYXKEYS.COLLECTION.REPORT}${reportID}] throws:
TypeError: Cannot read properties of undefined (reading 'report_...')
Every other lookup in useSearchBulkActions.ts (e.g. lines 753 and 2012) correctly uses currentSearchResults?.data?.[...]. Line 2924 is the only unguarded access in the entire file.
What changes do you think we should make in order to solve the problem?
In src/hooks/useSearchBulkActions.ts (line 2924), add optional chaining to .data:
When data is missing or undefined during loading states, every report lookup evaluates safely to undefined and is discarded by .filter(...). selectedMergeReports.length will not equal selectedReports.length, which gracefully suppresses the "Merge Reports" bulk action until search data has fully loaded.
What alternative solutions did you explore? (Optional)
Adding an explicit if (!currentSearchResults?.data) return [] guard: Using optional chaining ?. is cleaner, aligns with existing conventions in the file (lines 753, 2012), and minimizes boilerplate.
Automated Test Scenarios
Add a unit test in tests/unit/useSearchBulkActionsTest.ts:
describe('useSearchBulkActions',()=>{it('does not throw and omits merge bulk action when currentSearchResults.data is undefined',()=>{const{result}=renderHook(()=>useSearchBulkActions({currentSearchResults: {search: {isLoading: true}}asany,selectedReports: [{reportID: '12345'}asany],}),);expect(()=>result.current.bulkActions).not.toThrow();consthasMergeOption=result.current.bulkActions?.some((action)=>action.value===CONST.SEARCH.BULK_ACTION_TYPES.MERGE_REPORTS,);expect(hasMergeOption).toBe(false);});it('includes merge bulk action when currentSearchResults.data contains all selected reports',()=>{// Verify merge bulk action is offered when data is populated and merge conditions pass});});
Please re-state the problem that we are trying to solve in this issue.
In /search, performing or resetting bulk actions throws an unhandled \TypeError: Cannot read properties of undefined (reading 'report_')\ during render in \useSearchBulkActions.ts\ (Sentry: APP-KT0).
What is the root cause of that problem?
In \src/hooks/useSearchBulkActions.ts\ (line 2920 / line 2936):
\\ ypescript
const selectedMergeReports = areAllMatchingItemsSelected
? []
: selectedReports
.map(({reportID}) => currentSearchResults?.data[\\])
.filter((report) => !!report?.reportID && report.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
\
While all other calls across \useSearchBulkActions.ts\ safely access \currentSearchResults?.data?.[\\]\ (e.g. lines 752, 847, 1995, 2008, 2022, 2035, 2200, 2221), this mapping omits optional chaining on \currentSearchResults?.data.
When transitioning between search views or when the search query snapshot is reset while reports remain selected, \currentSearchResults?.data\ is \undefined, and indexing into it throws immediately during \useMemo\ render.
What changes do you think we should make in order to solve the problem?
What alternative solutions did you explore? (Optional)
Guarding \currentSearchResults?.data\ at the top of the option builder block: while functional, using ?.\ directly preserves idiomatic consistency with all other report snapshot readers in this hook.
What specific scenarios should we cover in automated tests?
In \ ests/unit/useSearchBulkActionsTest.ts:
Verify that building search bulk actions with selected reports when \currentSearchResults.data\ is \undefined\ or empty does not throw and returns empty merge options.
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.13-6 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-10-26. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ 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]. If no checklist is needed for this issue, you can click: [no checklist button]
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion: N/A
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
[BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.
Link to issue:
Regression Test Proposal
Platforms: wide layout only (desktop web, desktop app, or tablet in landscape). N/A on native phones, since leaving selection mode there clears the selection.
Log in with an account that has at least two expense reports.
Go to Spend > Reports.
Tick the checkbox on one or more reports so the bulk actions button appears.
Without clearing the selection, click Filters.
Click Date and select a preset you haven't used yet in this session (e.g. This month).
Verify that the search list shows a brief loading state, then the new results show, and the app doesn't crash.
Activity
melvin-bot commentedon Oct 12, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @ikevin127 (
External)[-][Sentry: APP-KT0] Search bulk actions crash reading undefined search results data[/-][+][$175] [Sentry: APP-KT0] Search bulk actions crash reading undefined search results data[/+]melvin-bot commentedon Oct 12, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022109653509046325310
haiquywork commentedon Oct 12, 2026
Proposal
What is the root cause of that problem?
In
useSearchBulkActions, theselectedMergeReportscomputation indexescurrentSearchResults?.data[...]. The?.only guardscurrentSearchResults. WhencurrentSearchResultsexists butdatais undefined (for example while the snapshot is loading or being cleared, with selected reports still in state),undefined[...]throws. Every other access to this object in the file usescurrentSearchResults?.data?.[...], so this is the only unguarded one.App/src/hooks/useSearchBulkActions.ts
Lines 2921 to 2924 in 6502fec
What changes do you think we should make in order to solve the problem?
useSearchBulkActions.ts(around line 2924), change the lookup to optional chaining ondataas well:datais missing, the lookup gives undefined, soselectedMergeReportsends up shorter thanselectedReportsand the merge option is not shown. This is the correct behaviour.useSearchBulkActionstests, "does not crash when search results have no data": render the hook with selected reports andcurrentSearchResultsset to{search: {...}}with nodata. Expect no throw and no merge option..data[access without?.. I found only this one.What alternative solutions did you explore? (Optional)
currentSearchResults?.datais undefined. This is more code for the same result.searchResults(the last non-empty ref). This changes behaviour, and the code elsewhere deliberately uses the current results.MelvinBot commentedon Oct 12, 2026
Proposal
What is the root cause of that problem?
The merge-reports check in
useSearchBulkActionsreadscurrentSearchResults?.data[...]. The?.guards onlycurrentSearchResults, notdata. This is the only unguardeddataaccess in the file.datacan be missing at runtime. When a search starts for a new hash, its optimistic update creates the snapshot with onlysearchanderrors.datastays missing until the response arrives, or for good if the request fails. Report selection lives in a separate provider, so selected reports can still exist on that render. The lookup then reads fromundefinedand throws.TypeScript does not catch this because
SearchResults.datais typed as required.Code references
data?.[...], for example L753 and L2012. The hook also has an explicit!currentSearchResults?.datacheck at L662-L667.data: src/libs/actions/Search.ts:803-826. The failure data also leavesdataunset: L846-L859.currentSearchResults: src/components/Search/SearchResultsProvider.tsx:71datais typed as required: src/types/onyx/SearchResults.ts:309I didn't reproduce this in the app. The root cause comes from the stack trace and the code path.
What changes do you think we should make in order to solve the problem?
useSearchBulkActions.ts, add optional chaining ondatain theselectedMergeReportslookup, so it readscurrentSearchResults?.data?.[...].datais missing, every lookup returnsundefinedand gets filtered out. The lengths then don't match, so the hook hides the Merge option until results load. That is the right behavior.currentSearchResultswithsearchbut nodata, the hook should not throw and should not offer Merge.What alternative solutions did you explore? (Optional)
Make
dataoptional in theSearchResultstype so TypeScript flags unguarded accesses like this one. This prevents repeats, such as the possible sibling APP-C89. But it touches many call sites, so it fits a separate cleanup better than this fix.Note: The existing proposal from
haiquyworkdescribes the same root cause and fix.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/103775)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
mountiny commentedon Oct 12, 2026
(Vit's agent) @ikevin127, can you review the proposal from MelvinBot on this issue? Please tell us which of these fits:
janiya2 commentedon Oct 12, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
[Sentry: APP-KT0] Unhandled
TypeError: Cannot read properties of undefined (reading 'report_<reportID>')occurring inuseSearchBulkActionson the/searchpage.What is the root cause of that problem?
In
src/hooks/useSearchBulkActions.ts(around lines 2921-2925):The optional chaining operator
?.only guardscurrentSearchResults, but fails to guard.data.When search results are loading, being refreshed, or right after an optimistic clear where
currentSearchResultshas the structure{search: {...}}withoutdata,currentSearchResults?.dataevaluates toundefined. If reports are currently selected in state, indexing intoundefined[${ONYXKEYS.COLLECTION.REPORT}${reportID}]throws:Every other lookup in
useSearchBulkActions.ts(e.g. lines 753 and 2012) correctly usescurrentSearchResults?.data?.[...]. Line 2924 is the only unguarded access in the entire file.What changes do you think we should make in order to solve the problem?
In
src/hooks/useSearchBulkActions.ts(line 2924), add optional chaining to.data:const selectedMergeReports = areAllMatchingItemsSelected ? [] : selectedReports - .map(({reportID}) => currentSearchResults?.data[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]) + .map(({reportID}) => currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]) .filter((report) => !!report?.reportID && report.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);When
datais missing or undefined during loading states, every report lookup evaluates safely toundefinedand is discarded by.filter(...).selectedMergeReports.lengthwill not equalselectedReports.length, which gracefully suppresses the "Merge Reports" bulk action until search data has fully loaded.What alternative solutions did you explore? (Optional)
if (!currentSearchResults?.data) return []guard: Using optional chaining?.is cleaner, aligns with existing conventions in the file (lines 753, 2012), and minimizes boilerplate.Automated Test Scenarios
Add a unit test in
tests/unit/useSearchBulkActionsTest.ts:Contributor Details
jloa-dev commentedon Oct 12, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
In /search, performing or resetting bulk actions throws an unhandled \TypeError: Cannot read properties of undefined (reading 'report_')\ during render in \useSearchBulkActions.ts\ (Sentry: APP-KT0).
What is the root cause of that problem?
In \src/hooks/useSearchBulkActions.ts\ (line 2920 / line 2936):
\\ ypescript
const selectedMergeReports = areAllMatchingItemsSelected
? []
: selectedReports
.map(({reportID}) => currentSearchResults?.data[\\])
.filter((report) => !!report?.reportID && report.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
\
While all other calls across \useSearchBulkActions.ts\ safely access \currentSearchResults?.data?.[\\]\ (e.g. lines 752, 847, 1995, 2008, 2022, 2035, 2200, 2221), this mapping omits optional chaining on \currentSearchResults?.data.
When transitioning between search views or when the search query snapshot is reset while reports remain selected, \currentSearchResults?.data\ is \undefined, and indexing into it throws immediately during \useMemo\ render.
What changes do you think we should make in order to solve the problem?
In \src/hooks/useSearchBulkActions.ts:
Add optional chaining to \currentSearchResults?.data:
\\ ypescript
const selectedMergeReports = areAllMatchingItemsSelected
? []
: selectedReports
.map(({reportID}) => currentSearchResults?.data?.[\\])
.filter((report) => !!report?.reportID && report.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
\\
What alternative solutions did you explore? (Optional)
What specific scenarios should we cover in automated tests?
In \ ests/unit/useSearchBulkActionsTest.ts:
Contributor details
Your Expensify account email: jloa.dev@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~0163955a2ba22190aa
8 remaining items
[-][$175] [Sentry: APP-KT0] Search bulk actions crash reading undefined search results data[/-][+][Due for payment 2026-10-26] [$175] [Sentry: APP-KT0] Search bulk actions crash reading undefined search results data[/+]melvin-bot commentedon Oct 19, 2026
@ikevin127
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.13-6 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-10-26. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ 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]. If no checklist is needed for this issue, you can click: [no checklist button]
ikevin127 commentedon Oct 19, 2026
BugZero Checklist
[Contributor] The offending PR has been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
Link to comment: https://github.com/Expensify/App/pull/95658/changes#r4318638431
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion: N/A
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
[BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.
Link to issue:
Regression Test Proposal
Platforms: wide layout only (desktop web, desktop app, or tablet in landscape). N/A on native phones, since leaving selection mode there clears the selection.
Do we agree 👍 or 👎.