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
[$175] [Sentry: APP-JQB] FlashList "right operand of 'in' is not an object" in participant selector #103776
getDataKey in the FlashList data processing runs the 'in' operator on a value that is not an object. The data comes from BaseSelectionListWithSections inside MoneyRequestParticipantsSelector.
Reproduction
Unknown. See the events linked from the Sentry issue.
changed the title [-][Sentry: APP-JQB] FlashList "right operand of 'in' is not an object" in participant selector[/-][+][$175] [Sentry: APP-JQB] FlashList "right operand of 'in' is not an object" in participant selector[/+]on Oct 12, 2026
(Vit's agent) @thelullabyy, there is no proposal on this issue yet. Can you look at the Sentry issue and tell us whether we should open it for contributors as it stands, or ask for expert contributor help because it needs deeper FlashList knowledge?
Please re-state the problem that we are trying to solve in this issue.
A runtime TypeError ("right operand of 'in' is not an object") crashes the application on iOS during render in the participant selector when creating an expense.
What is the root cause of that problem?
The crash occurs in src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx at line 367: keyExtractor={(item) => ('flatListKey' in item ? item.flatListKey : item.keyForList)}
When FlashList performs list recycling, layout measurement, or data updates in useRecyclerViewManager.processDataUpdate -> getDataKey, keyExtractor can be invoked with an item that is undefined or not an object (for example, during rapid search filter transitions, list resets, or if section.data contains uninitialized entries).
In JavaScript, evaluating the in operator on a non-object ('flatListKey' in undefined) immediately throws TypeError: right operand of 'in' is not an object in JavaScriptCore on iOS.
By contrast, the flat list variant in src/components/SelectionList/BaseSelectionList.tsx (line 523) safely guards the item with optional chaining and provides a fallback to the index string: keyExtractor={(item, index) => item?.keyForList ?? index.toString()}
In BaseSelectionListWithSections.tsx, keyExtractor lacks both the object type guard and the index fallback.
What changes do you think we should make in order to solve the problem?
In src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx:
Update keyExtractor to accept (item, index) and guard against null or non-object values before evaluating property access:
Check if item is a valid object (item && typeof item === 'object').
If valid, return item.flatListKey when present (or item.keyForList).
Fall back safely to index.toString() if item is null, undefined, or missing key properties.
In src/components/SelectionList/hooks/useFlattenedSections.ts:
In the inner loop iterating over section.data, add an explicit guard (if (!item || typeof item !== 'object') continue;) to prevent null or undefined entries from being pushed into flattenedData during dynamic search calculations.
In automated tests:
Add unit test coverage in tests/unit/BaseSelectionListSectionsTest.tsx asserting that:
keyExtractor safely returns a deterministic key when given a standard row item with flatListKey.
keyExtractor safely returns keyForList when given a section header.
keyExtractor returns index.toString() without throwing when invoked with undefined or null.
What alternative solutions did you explore? (Optional)
I explored filtering nullish items only inside ParticipantSearchResults.tsx. However, patching individual consumer screens is fragile because any screen utilizing SelectionListWithSections (such as workspace members, approver picker, or currency picker) could trigger the same crash if FlashList invokes keyExtractor during internal data recycling. Hardening keyExtractor directly in BaseSelectionListWithSections and useFlattenedSections resolves the issue at the framework layer for all consumers, matching the defensive pattern already used in BaseSelectionList.tsx.
FlashList asks for a key for an item index that is past the end of the current list. Our keyExtractor then runs 'flatListKey' in undefined, which throws.
This happens because the participants list is wrapped in <Activity>. When the screen blurs, Activity hides the list and runs its effect cleanups, but the list stays mounted:
When the list is hidden, FlashList's effect cleanup calls recyclerViewManager.dispose(). A disposed manager skips updateScrollOffset, so it never refreshes its engaged indices (the item indices it keeps rendered).
The hidden list still re-renders when the sections change. If the list gets shorter, processDataUpdate reuses the old engaged indices. Then getDataKey calls keyExtractor(data[index]) with data[index] === undefined.
The flattened data itself is fine. useFlattenedSections.ts only pushes object literals. The undefined comes from an out-of-range index, not from a bad entry.
What changes do you think we should make in order to solve the problem?
Make the keyExtractor in BaseSelectionListWithSections null-safe. When item is missing, fall back to index.toString().
This matches existing code:
BaseSelectionList.tsx:523 already does item?.keyForList ?? index.toString(). Its comment notes that FlashList can pass a stale index after the dataset shrinks.
getItemType in the same file (line 36) already uses item?.type.
FlashList recycles the stale slots on its next sync, after the list becomes visible again.
What alternative solutions did you explore? (Optional)
Add a @shopify/flash-list patch (017) so FlashList never uses stale indices. Either skip processDataUpdate while the manager is disposed, or ignore engaged indices >= data.length in updateRenderStack. This would protect every FlashList that renders under Activity. It is the same kind of bug that patch 016 fixed on another code path, in Guard FlashList ViewHolderCollection render against stale layout index (web crash). We can do this as well as the app-side guard. It is not a replacement for the guard.
I didn't choose filtering falsy items in useFlattenedSections. The data never contains falsy items, so the filter wouldn't fix the crash.
Confidence and open questions
Mechanism: high confidence. In FlashList 2.3.2 plus our patches, this is the only path where processDataUpdate uses an out-of-range index. React disconnects passive effects only for Activity/Offscreen, not for Suspense or react-freeze. That explains why the crash started only after the Activity change.
Trigger PR: medium-high confidence. The timing and the code match. I didn't confirm that the first-seen release contains it, because Sentry wasn't reachable from this run.
Not reproduced on a device. The exact data change while the list is hidden is unknown. The likely candidates are a selection or participants reset after picking someone, or an Onyx or search refresh during the blur or refocus transition. Sentry breadcrumbs around navigation would confirm.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/103776) 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-JQB] Unhandled TypeError: right operand of 'in' is not an object (or Cannot use 'in' operator to search for 'flatListKey' in undefined) in MoneyRequestParticipantsSelector / BaseSelectionListWithSections on iOS/Android.
What is the root cause of that problem?
The crash occurs because FlashList evaluates keyExtractor with an out-of-range/stale index (data[index] === undefined) when the list dataset shrinks or updates while blurred under <Activity>:
The <Activity> Blur Lifecycle:
In src/pages/iou/request/MoneyRequestParticipantsSelector.tsx (line 114), the participants list is wrapped in: <Activity mode={isActivityVisible ? 'visible' : 'hidden'}>
When the user selects a participant or navigates away, the screen blurs, triggering the Activity hidden state. Under Activity hidden mode, FlashList's cleanup calls recyclerViewManager.dispose(). When disposed, updateScrollOffset is suspended, leaving the cached "engaged indices" (the range of item indices FlashList keeps rendered) untouched.
Dataset Shrinking and Stale Engaged Indices:
When sections or participant results update (e.g. selection resets or search query clear), the new flattenedData length is shorter than the previously cached engaged indices. During processDataUpdate, FlashList iterates over these stale engaged indices and calls getDataKey(index), which executes: keyExtractor(data[index], index)
Because index >= data.length, data[index] evaluates to undefined.
Missing Guard in BaseSelectionListWithSections.tsx:
In src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx (line 367):
Unlike its flat counterpart in BaseSelectionList.tsx (lines 522-523), which explicitly documents this behavior:
// FlashList can pass a stale index here while its render stack syncs after the dataset shrinks, so item can be undefined.keyExtractor={(item,index)=>item?.keyForList??index.toString()}
BaseSelectionListWithSections.tsx neither accepts the second index argument nor guards against item being undefined. Evaluating 'flatListKey' in undefined throws: TypeError: right operand of 'in' is not an object.
(Note: The data emitted by useFlattenedSections.ts is completely valid and only contains constructed object literals; the undefined is strictly produced by FlashList's out-of-bounds indexing during data updates).
What changes do you think we should make in order to solve the problem?
In src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx (line 367), update keyExtractor to accept (item, index) and guard against null/undefined:
- keyExtractor={(item) => ('flatListKey' in item ? item.flatListKey : item.keyForList)}+ // FlashList can pass a stale index here while its render stack syncs after the dataset shrinks, so item can be undefined.+ keyExtractor={(item, index) => (item && typeof item === 'object' && 'flatListKey' in item ? item.flatListKey : (item?.keyForList ?? index.toString()))}
This brings BaseSelectionListWithSections into full parity with the defensive pattern already established in BaseSelectionList.tsx:523 and getItemType (BaseSelectionListWithSections.tsx:36). When FlashList syncs on the subsequent pass, it safely recycles the stale slots without crashing.
What alternative solutions did you explore? (Optional)
Filtering in useFlattenedSections.ts:
Filtering falsy items inside useFlattenedSections.ts does not solve the crash because useFlattenedSections.ts never produces undefined items—the undefined value originates inside FlashList when indexing beyond flattenedData.length.
Patching @shopify/flash-list:
While a patch could be added to ignore out-of-bounds indices in RecyclerViewManager.ts, handling the fallback in keyExtractor adheres to Expensify's standard architecture in BaseSelectionList.tsx and prevents requiring patch maintenance across future FlashList upgrades.
Automated Test Scenarios
Add a unit test in tests/unit/BaseSelectionListWithSectionsTest.tsx:
describe('BaseSelectionListWithSections keyExtractor',()=>{// Extracted keyExtractor logic under testconstkeyExtractor=(item: any,index: number)=>item&&typeofitem==='object'&&'flatListKey'initem ? item.flatListKey : (item?.keyForList??index.toString());it('does not throw and falls back to index string when item is undefined (FlashList dataset shrinking)',()=>{expect(()=>keyExtractor(undefined,5)).not.toThrow();expect(keyExtractor(undefined,5)).toBe('5');});it('does not throw and falls back to index string when item is null',()=>{expect(()=>keyExtractor(null,3)).not.toThrow();expect(keyExtractor(null,3)).toBe('3');});it('extracts flatListKey when item is a section row item',()=>{constitem={flatListKey: '0-user_123',keyForList: 'user_123'};expect(keyExtractor(item,0)).toBe('0-user_123');});it('extracts keyForList when item is a section header',()=>{constitem={keyForList: 'section_header_0'};expect(keyExtractor(item,0)).toBe('section_header_0');});it('falls back to index string when object lacks both flatListKey and keyForList',()=>{constitem={};expect(keyExtractor(item,4)).toBe('4');});});
Please re-state the problem that we are trying to solve in this issue.
In \MoneyRequestParticipantsSelector, navigating or filtering participants throws an unhandled \TypeError: right operand of 'in' is not an object\ in \keyExtractor\ inside \BaseSelectionListWithSections.tsx\ (Sentry: APP-JQB).
What is the root cause of that problem?
In \src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx\ (line 367):
\\ ypescript\keyExtractor={(item) => ('flatListKey' in item ? item.flatListKey : item.keyForList)}
\
When \MoneyRequestParticipantsSelector\ is blurred or wrapped in <Activity>, FlashList disposes its RecyclerView manager during effect cleanups without updating scroll/engaged indices. If the participant sections shrink (e.g. searching, clearing, or resetting participants), FlashList reuses engaged indices that exceed the newly shrunk dataset, passing \undefined\ to \keyExtractor(data[index]).
Because \item\ is \undefined, 'flatListKey' in undefined\ throws \TypeError: right operand of 'in' is not an object.
By contrast, \BaseSelectionList.tsx\ (line 523) already guards this with \item?.keyForList ?? index.toString(), noting that FlashList can query stale indices after dataset shrinkage.
What changes do you think we should make in order to solve the problem?
In \src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx:
Make \keyExtractor\ null-safe and fall back to \index.toString():
\\ ypescript
keyExtractor={(item, index) => (item && 'flatListKey' in item ? item.flatListKey : item?.keyForList ?? index.toString())}
\\
What alternative solutions did you explore? (Optional)
Filtering out falsy items in \useFlattenedSections.ts: Unnecessary because the flattened sections only contain valid objects; the out-of-range index comes directly from FlashList's internal recycling stack.
Patching FlashList: Adding null guards directly to consumer components is idiomatic and matches the pattern already in \BaseSelectionList.tsx.
What specific scenarios should we cover in automated tests?
In \ ests/unit/BaseSelectionListWithSectionsTest.tsx:
Verify that \keyExtractor\ returns \index.toString()\ without throwing when passed \undefined\ or null item.
Verify that valid section headers and list items extract \latListKey\ and \keyForList\ correctly.
Please re-state the problem that we are trying to solve in this issue.
In MoneyRequestParticipantsSelector and list views using BaseSelectionListWithSections, when the list is hidden or dataset items shrink during activity state transitions, keyExtractor crashes with TypeError: Cannot use 'in' operator to search for 'flatListKey' in undefined due to stale index evaluation on item without verifying that item is a valid object.
What is the root cause of that problem?
In BaseSelectionListWithSections.tsx:367, keyExtractor evaluates:
'flatListKey'initem
without verifying that item is a valid object.
When MoneyRequestParticipantsSelector blurs, it wraps the list in <Activity mode={isActivityVisible ? 'visible' : 'hidden'}>. Hiding the component invokes recyclerViewManager.dispose() in FlashList, which halts scroll offset recalculations.
If the underlying flattened sections shrink while the list remains mounted and hidden, FlashList's processDataUpdate attempts to reconcile layout using stale engaged indices that exceed the new dataset bounds. FlashList then calls keyExtractor(data[index], index) with data[index] === undefined.
Evaluating 'flatListKey' in undefined throws: TypeError: Cannot use 'in' operator to search for 'flatListKey' in undefined.
What changes do you think we should make in order to solve the problem?
In src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx:367, guard item in keyExtractor before checking 'flatListKey':
If item is undefined (stale out-of-range index), safely fall back to index.toString().
This mirrors the existing pattern already implemented in src/components/SelectionList/BaseSelectionList.tsx:523 (item?.keyForList ?? index.toString()).
2. Apply the same defensive guard to getItemType in BaseSelectionListWithSections.tsx:36 to ensure stale indices don't trigger subsequent runtime errors.
3. Add a unit test in tests/unit/BaseSelectionListWithSectionsTest.tsx:
Pass an undefined item to keyExtractor and assert that it returns index.toString() without throwing.
What alternative solutions did you explore? (Optional)
Unmounting the list instead of toggling Activity visibility: Rejected because keeping the list mounted preserves scroll positions and layout caches for fast tab switching. Defensive guarding in keyExtractor matches the sibling BaseSelectionList pattern directly.
Activity
melvin-bot commentedon Oct 12, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @thelullabyy (
External)[-][Sentry: APP-JQB] FlashList "right operand of 'in' is not an object" in participant selector[/-][+][$175] [Sentry: APP-JQB] FlashList "right operand of 'in' is not an object" in participant selector[/+]melvin-bot commentedon Oct 12, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022109653539631190078
mountiny commentedon Oct 12, 2026
(Vit's agent) @thelullabyy, there is no proposal on this issue yet. Can you look at the Sentry issue and tell us whether we should open it for contributors as it stands, or ask for expert contributor help because it needs deeper FlashList knowledge?
taieddie commentedon Oct 12, 2026
Contributor details
Your Expensify account email: taieddie8@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~01af851c9a7f771775
Proposal
Please re-state the problem that we are trying to solve in this issue.
A runtime TypeError ("right operand of 'in' is not an object") crashes the application on iOS during render in the participant selector when creating an expense.
What is the root cause of that problem?
The crash occurs in
src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsxat line 367:keyExtractor={(item) => ('flatListKey' in item ? item.flatListKey : item.keyForList)}When
FlashListperforms list recycling, layout measurement, or data updates inuseRecyclerViewManager.processDataUpdate -> getDataKey,keyExtractorcan be invoked with an item that isundefinedor not an object (for example, during rapid search filter transitions, list resets, or ifsection.datacontains uninitialized entries).In JavaScript, evaluating the
inoperator on a non-object ('flatListKey' in undefined) immediately throwsTypeError: right operand of 'in' is not an objectin JavaScriptCore on iOS.By contrast, the flat list variant in
src/components/SelectionList/BaseSelectionList.tsx(line 523) safely guards the item with optional chaining and provides a fallback to the index string:keyExtractor={(item, index) => item?.keyForList ?? index.toString()}In
BaseSelectionListWithSections.tsx,keyExtractorlacks both the object type guard and the index fallback.What changes do you think we should make in order to solve the problem?
src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx:Update
keyExtractorto accept(item, index)and guard against null or non-object values before evaluating property access:itemis a valid object (item && typeof item === 'object').item.flatListKeywhen present (oritem.keyForList).index.toString()ifitemis null, undefined, or missing key properties.In
src/components/SelectionList/hooks/useFlattenedSections.ts:In the inner loop iterating over
section.data, add an explicit guard (if (!item || typeof item !== 'object') continue;) to prevent null or undefined entries from being pushed intoflattenedDataduring dynamic search calculations.In automated tests:
Add unit test coverage in
tests/unit/BaseSelectionListSectionsTest.tsxasserting that:keyExtractorsafely returns a deterministic key when given a standard row item withflatListKey.keyExtractorsafely returnskeyForListwhen given a section header.keyExtractorreturnsindex.toString()without throwing when invoked withundefinedornull.What alternative solutions did you explore? (Optional)
I explored filtering nullish items only inside
ParticipantSearchResults.tsx. However, patching individual consumer screens is fragile because any screen utilizingSelectionListWithSections(such as workspace members, approver picker, or currency picker) could trigger the same crash ifFlashListinvokeskeyExtractorduring internal data recycling. HardeningkeyExtractordirectly inBaseSelectionListWithSectionsanduseFlattenedSectionsresolves the issue at the framework layer for all consumers, matching the defensive pattern already used inBaseSelectionList.tsx.melvin-bot commentedon Oct 12, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
MelvinBot commentedon Oct 12, 2026
Proposal
What is the root cause of that problem?
FlashList asks for a key for an item index that is past the end of the current list. Our
keyExtractorthen runs'flatListKey' in undefined, which throws.This happens because the participants list is wrapped in
<Activity>. When the screen blurs,Activityhides the list and runs its effect cleanups, but the list stays mounted:<Activity mode={isActivityVisible ? 'visible' : 'hidden'}>. Deferring & deprioritizing participant search render added it. That PR merged on 2026-07-27, six days before this crash was first seen.recyclerViewManager.dispose(). A disposed manager skipsupdateScrollOffset, so it never refreshes its engaged indices (the item indices it keeps rendered).processDataUpdatereuses the old engaged indices. ThengetDataKeycallskeyExtractor(data[index])withdata[index] === undefined.keyExtractorat BaseSelectionListWithSections.tsx:367 has no guard, so it throws.The flattened data itself is fine. useFlattenedSections.ts only pushes object literals. The
undefinedcomes from an out-of-range index, not from a bad entry.What changes do you think we should make in order to solve the problem?
Make the
keyExtractorinBaseSelectionListWithSectionsnull-safe. Whenitemis missing, fall back toindex.toString().This matches existing code:
item?.keyForList ?? index.toString(). Its comment notes that FlashList can pass a stale index after the dataset shrinks.getItemTypein the same file (line 36) already usesitem?.type.FlashList recycles the stale slots on its next sync, after the list becomes visible again.
What alternative solutions did you explore? (Optional)
Add a
@shopify/flash-listpatch (017) so FlashList never uses stale indices. Either skipprocessDataUpdatewhile the manager is disposed, or ignore engaged indices>= data.lengthinupdateRenderStack. This would protect every FlashList that renders underActivity. It is the same kind of bug that patch 016 fixed on another code path, in Guard FlashList ViewHolderCollection render against stale layout index (web crash). We can do this as well as the app-side guard. It is not a replacement for the guard.I didn't choose filtering falsy items in
useFlattenedSections. The data never contains falsy items, so the filter wouldn't fix the crash.Confidence and open questions
processDataUpdateuses an out-of-range index. React disconnects passive effects only forActivity/Offscreen, not for Suspense orreact-freeze. That explains why the crash started only after theActivitychange.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/103776)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
janiya2 commentedon Oct 12, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
[Sentry: APP-JQB] Unhandled
TypeError: right operand of 'in' is not an object(orCannot use 'in' operator to search for 'flatListKey' in undefined) inMoneyRequestParticipantsSelector/BaseSelectionListWithSectionson iOS/Android.What is the root cause of that problem?
The crash occurs because FlashList evaluates
keyExtractorwith an out-of-range/stale index (data[index] === undefined) when the list dataset shrinks or updates while blurred under<Activity>:The
<Activity>Blur Lifecycle:In
src/pages/iou/request/MoneyRequestParticipantsSelector.tsx(line 114), the participants list is wrapped in:<Activity mode={isActivityVisible ? 'visible' : 'hidden'}>When the user selects a participant or navigates away, the screen blurs, triggering the
Activityhidden state. UnderActivityhidden mode, FlashList's cleanup callsrecyclerViewManager.dispose(). When disposed,updateScrollOffsetis suspended, leaving the cached "engaged indices" (the range of item indices FlashList keeps rendered) untouched.Dataset Shrinking and Stale Engaged Indices:
When sections or participant results update (e.g. selection resets or search query clear), the new
flattenedDatalength is shorter than the previously cached engaged indices. DuringprocessDataUpdate, FlashList iterates over these stale engaged indices and callsgetDataKey(index), which executes:keyExtractor(data[index], index)Because
index >= data.length,data[index]evaluates toundefined.Missing Guard in
BaseSelectionListWithSections.tsx:In
src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx(line 367):Unlike its flat counterpart in
BaseSelectionList.tsx(lines 522-523), which explicitly documents this behavior:BaseSelectionListWithSections.tsxneither accepts the secondindexargument nor guards againstitembeingundefined. Evaluating'flatListKey' in undefinedthrows:TypeError: right operand of 'in' is not an object.(Note: The data emitted by
useFlattenedSections.tsis completely valid and only contains constructed object literals; theundefinedis strictly produced by FlashList's out-of-bounds indexing during data updates).What changes do you think we should make in order to solve the problem?
In
src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx(line 367), updatekeyExtractorto accept(item, index)and guard against null/undefined:This brings
BaseSelectionListWithSectionsinto full parity with the defensive pattern already established inBaseSelectionList.tsx:523andgetItemType(BaseSelectionListWithSections.tsx:36). When FlashList syncs on the subsequent pass, it safely recycles the stale slots without crashing.What alternative solutions did you explore? (Optional)
useFlattenedSections.ts:Filtering falsy items inside
useFlattenedSections.tsdoes not solve the crash becauseuseFlattenedSections.tsnever produces undefined items—theundefinedvalue originates inside FlashList when indexing beyondflattenedData.length.@shopify/flash-list:While a patch could be added to ignore out-of-bounds indices in
RecyclerViewManager.ts, handling the fallback inkeyExtractoradheres to Expensify's standard architecture inBaseSelectionList.tsxand prevents requiring patch maintenance across future FlashList upgrades.Automated Test Scenarios
Add a unit test in
tests/unit/BaseSelectionListWithSectionsTest.tsx:Contributor Details
jloa-dev commentedon Oct 12, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
In \MoneyRequestParticipantsSelector, navigating or filtering participants throws an unhandled \TypeError: right operand of 'in' is not an object\ in \keyExtractor\ inside \BaseSelectionListWithSections.tsx\ (Sentry: APP-JQB).
What is the root cause of that problem?
In \src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx\ (line 367):
\\ ypescript\keyExtractor={(item) => ('flatListKey' in item ? item.flatListKey : item.keyForList)}
\
When \MoneyRequestParticipantsSelector\ is blurred or wrapped in <Activity>, FlashList disposes its RecyclerView manager during effect cleanups without updating scroll/engaged indices. If the participant sections shrink (e.g. searching, clearing, or resetting participants), FlashList reuses engaged indices that exceed the newly shrunk dataset, passing \undefined\ to \keyExtractor(data[index]).
Because \item\ is \undefined, 'flatListKey' in undefined\ throws \TypeError: right operand of 'in' is not an object.
By contrast, \BaseSelectionList.tsx\ (line 523) already guards this with \item?.keyForList ?? index.toString(), noting that FlashList can query stale indices after dataset shrinkage.
What changes do you think we should make in order to solve the problem?
In \src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx:
Make \keyExtractor\ null-safe and fall back to \index.toString():
\\ ypescript
keyExtractor={(item, index) => (item && 'flatListKey' in item ? item.flatListKey : item?.keyForList ?? index.toString())}
\\
What alternative solutions did you explore? (Optional)
What specific scenarios should we cover in automated tests?
In \ ests/unit/BaseSelectionListWithSectionsTest.tsx:
Contributor details
Your Expensify account email: jloa.dev@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~0163955a2ba22190aa
melvin-bot commentedon Oct 12, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
camesenin commentedon Oct 13, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
In
MoneyRequestParticipantsSelectorand list views usingBaseSelectionListWithSections, when the list is hidden or dataset items shrink during activity state transitions,keyExtractorcrashes withTypeError: Cannot use 'in' operator to search for 'flatListKey' in undefineddue to stale index evaluation onitemwithout verifying thatitemis a valid object.What is the root cause of that problem?
In
BaseSelectionListWithSections.tsx:367,keyExtractorevaluates:without verifying that
itemis a valid object.When
MoneyRequestParticipantsSelectorblurs, it wraps the list in<Activity mode={isActivityVisible ? 'visible' : 'hidden'}>. Hiding the component invokesrecyclerViewManager.dispose()in FlashList, which halts scroll offset recalculations.If the underlying flattened sections shrink while the list remains mounted and hidden, FlashList's
processDataUpdateattempts to reconcile layout using stale engaged indices that exceed the new dataset bounds. FlashList then callskeyExtractor(data[index], index)withdata[index] === undefined.Evaluating
'flatListKey' in undefinedthrows:TypeError: Cannot use 'in' operator to search for 'flatListKey' in undefined.What changes do you think we should make in order to solve the problem?
src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx:367, guarditeminkeyExtractorbefore checking'flatListKey':If
itemis undefined (stale out-of-range index), safely fall back toindex.toString().This mirrors the existing pattern already implemented in
src/components/SelectionList/BaseSelectionList.tsx:523(item?.keyForList ?? index.toString()).2. Apply the same defensive guard to
getItemTypeinBaseSelectionListWithSections.tsx:36to ensure stale indices don't trigger subsequent runtime errors.3. Add a unit test in
tests/unit/BaseSelectionListWithSectionsTest.tsx:undefineditem tokeyExtractorand assert that it returnsindex.toString()without throwing.What alternative solutions did you explore? (Optional)
Activityvisibility: Rejected because keeping the list mounted preserves scroll positions and layout caches for fast tab switching. Defensive guarding inkeyExtractormatches the siblingBaseSelectionListpattern directly.Contributor details
Your Expensify account email: camesenin@gmail.com
Upwork Profile link: https://www.upwork.com/freelancers/~0170d6b02e9440e604
melvin-bot commentedon Oct 13, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!