From 58b321ffff3f34fed33770dfda33a678d12de81c Mon Sep 17 00:00:00 2001 From: VH Date: Sun, 15 Feb 2026 16:18:12 +0700 Subject: [PATCH 1/9] Migrate ShareTab from useOptionsList to useFilteredOptions --- src/pages/Share/ShareTab.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pages/Share/ShareTab.tsx b/src/pages/Share/ShareTab.tsx index 372f8776495a5..d5f9ad0ac1c9d 100644 --- a/src/pages/Share/ShareTab.tsx +++ b/src/pages/Share/ShareTab.tsx @@ -2,7 +2,7 @@ import type {Ref} from 'react'; import React, {useEffect, useImperativeHandle, useMemo, useRef} from 'react'; import {View} from 'react-native'; import {usePersonalDetails} from '@components/OnyxListItemProvider'; -import {useOptionsList} from '@components/OptionListContextProvider'; +import useFilteredOptions from '@hooks/useFilteredOptions'; import SelectionList from '@components/SelectionList'; import InviteMemberListItem from '@components/SelectionList/ListItem/InviteMemberListItem'; import type {ListItem, SelectionListHandle} from '@components/SelectionList/types'; @@ -61,8 +61,12 @@ function ShareTab({ref}: ShareTabProps) { focus: selectionListRef.current?.focusTextInput, })); - const {options, areOptionsInitialized} = useOptionsList(); const {didScreenTransitionEnd} = useScreenWrapperTransitionStatus(); + const {options: listOptions, isLoading} = useFilteredOptions({ + enabled: didScreenTransitionEnd, + betas: betas ?? [], + }); + const areOptionsInitialized = !isLoading; const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const offlineMessage: string = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; @@ -73,7 +77,7 @@ function ShareTab({ref}: ShareTabProps) { return defaultListOptions; } return getSearchOptions({ - options, + options: listOptions ?? {reports: [], personalDetails: []}, draftComments, nvpDismissedProductTraining, betas: betas ?? [], @@ -89,7 +93,7 @@ function ShareTab({ref}: ShareTabProps) { policyCollection: allPolicies, personalDetails, }); - }, [areOptionsInitialized, options, draftComments, nvpDismissedProductTraining, betas, textInputValue, countryCode, loginList, currentUserAccountID, currentUserEmail, personalDetails]); + }, [areOptionsInitialized, listOptions, draftComments, nvpDismissedProductTraining, betas, textInputValue, countryCode, loginList, currentUserAccountID, currentUserEmail, allPolicies, personalDetails]); const recentReportsOptions = useMemo(() => { if (textInputValue.trim() === '') { From d98e962369a5681422c597fb68592f145031b909 Mon Sep 17 00:00:00 2001 From: VH Date: Sun, 15 Feb 2026 20:55:21 +0700 Subject: [PATCH 2/9] Add searchTerm parameter to useFilteredOptions for avatar display --- src/pages/Share/ShareTab.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/pages/Share/ShareTab.tsx b/src/pages/Share/ShareTab.tsx index d5f9ad0ac1c9d..bf2426fc8f7f8 100644 --- a/src/pages/Share/ShareTab.tsx +++ b/src/pages/Share/ShareTab.tsx @@ -65,6 +65,7 @@ function ShareTab({ref}: ShareTabProps) { const {options: listOptions, isLoading} = useFilteredOptions({ enabled: didScreenTransitionEnd, betas: betas ?? [], + searchTerm: debouncedTextInputValue, }); const areOptionsInitialized = !isLoading; const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); @@ -93,7 +94,20 @@ function ShareTab({ref}: ShareTabProps) { policyCollection: allPolicies, personalDetails, }); - }, [areOptionsInitialized, listOptions, draftComments, nvpDismissedProductTraining, betas, textInputValue, countryCode, loginList, currentUserAccountID, currentUserEmail, allPolicies, personalDetails]); + }, [ + areOptionsInitialized, + listOptions, + draftComments, + nvpDismissedProductTraining, + betas, + textInputValue, + countryCode, + loginList, + currentUserAccountID, + currentUserEmail, + allPolicies, + personalDetails, + ]); const recentReportsOptions = useMemo(() => { if (textInputValue.trim() === '') { From fe9e4113bcc519ca53e55252ee95c258fadb1e91 Mon Sep 17 00:00:00 2001 From: VH Date: Fri, 6 Mar 2026 18:27:59 +0700 Subject: [PATCH 3/9] Update createFilteredOptionList to handle search mode efficiently - Extract searchTerm?.trim() check to isSearching boolean constant - Skip sorting in search mode since all reports are returned anyway - Add comment explaining the sort optimization for search case --- src/libs/OptionsListUtils/index.ts | 20 ++++--- tests/perf-test/OptionsListUtils.perf-test.ts | 59 ++++++++++++++++++- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 937dbb0394537..371cdbcc2f0a0 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -1492,6 +1492,7 @@ function createFilteredOptionList( visibleReportActionsData: VisibleReportActionsDerivedValue = {}, ) { const {maxRecentReports = 500, includeP2P = true, searchTerm = ''} = options; + const isSearching = !!searchTerm?.trim(); const reportMapForAccountIDs: Record = {}; // Step 1: Pre-filter reports to avoid processing thousands @@ -1501,19 +1502,24 @@ function createFilteredOptionList( }); // Step 2: Sort by lastVisibleActionCreated (most recent first) - const sortedReports = reportsArray.sort((a, b) => { - const aTime = new Date(a.lastVisibleActionCreated ?? 0).getTime(); - const bTime = new Date(b.lastVisibleActionCreated ?? 0).getTime(); - return bTime - aTime; - }); + // In search mode, skip sorting because we return all reports anyway - sorting is unnecessary + const sortedReports = isSearching + ? reportsArray + : reportsArray.sort((a, b) => { + const aTime = new Date(a.lastVisibleActionCreated ?? 0).getTime(); + const bTime = new Date(b.lastVisibleActionCreated ?? 0).getTime(); + return bTime - aTime; + }); // Step 3: Limit to top N reports - const limitedReports = sortedReports.slice(0, maxRecentReports); + // In search mode, we will return all reports so downstream getSearchOptions will handle the actual term matching + const effectiveLimit = isSearching ? reportsArray.length : maxRecentReports; + const limitedReports = sortedReports.slice(0, effectiveLimit); // Step 4: If search term is present, build report map with ONLY 1:1 DM reports // This allows personal details to have valid 1:1 DM reportIDs for proper avatar display // Users without 1:1 DMs will have no report mapped, causing getIcons to fall back to personal avatar - if (searchTerm?.trim()) { + if (isSearching) { const allReportsArray = Object.values(reports ?? {}); // Add ONLY 1:1 DM reports (never add group/policy chats to maintain personal avatars) diff --git a/tests/perf-test/OptionsListUtils.perf-test.ts b/tests/perf-test/OptionsListUtils.perf-test.ts index 61bbf6e0cce63..44f8a671aaaef 100644 --- a/tests/perf-test/OptionsListUtils.perf-test.ts +++ b/tests/perf-test/OptionsListUtils.perf-test.ts @@ -3,7 +3,7 @@ import type * as NativeNavigation from '@react-navigation/native'; import Onyx from 'react-native-onyx'; import {measureFunction} from 'reassure'; import type {PrivateIsArchivedMap} from '@hooks/usePrivateIsArchivedMap'; -import {createOptionList, filterAndOrderOptions, getMemberInviteOptions, getSearchOptions, getValidOptions} from '@libs/OptionsListUtils'; +import {createOptionList, filterAndOrderOptions, getMemberInviteOptions, getSearchOptions, getValidOptions, createFilteredOptionList} from '@libs/OptionsListUtils'; import type {OptionData} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -20,7 +20,7 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; const REPORTS_COUNT = 5000; const PERSONAL_DETAILS_LIST_COUNT = 1000; -const SEARCH_VALUE = 'TestingValue'; +const SEARCH_VALUE = 'Report'; const COUNTRY_CODE = 1; const PERSONAL_DETAILS_COUNT = 1000; @@ -285,4 +285,59 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); await measureFunction(() => formatSectionsFromSearchTerm('', Object.values(selectedOptions), [], [], MOCK_CURRENT_USER_ACCOUNT_ID, mockedPersonalDetails, true)); }); + + test('[OptionsListUtils] createFilteredOptionList', async () => { + await waitForBatchedUpdates(); + await measureFunction(() => + createFilteredOptionList( + personalDetails, + mockedReportsMap, + MOCK_CURRENT_USER_ACCOUNT_ID, + undefined, + EMPTY_PRIVATE_IS_ARCHIVED_MAP, + {maxRecentReports: 500, searchTerm: ''}, + ), + ); + }); + + test("[OptionsListUtils] createFilteredOptionList with searchTerm", async () => { + await waitForBatchedUpdates(); + await measureFunction(() => + createFilteredOptionList( + personalDetails, + mockedReportsMap, + MOCK_CURRENT_USER_ACCOUNT_ID, + undefined, + EMPTY_PRIVATE_IS_ARCHIVED_MAP, + {maxRecentReports: 500,searchTerm: SEARCH_VALUE} + ), + ); + }); + + test('[OptionsListUtils] getSearchOptions with searchTerm', async () => { + await waitForBatchedUpdates(); + const optionLists = createFilteredOptionList( + personalDetails, + mockedReportsMap, + MOCK_CURRENT_USER_ACCOUNT_ID, + undefined, + EMPTY_PRIVATE_IS_ARCHIVED_MAP, + {maxRecentReports: 500,searchTerm: SEARCH_VALUE} + ); + + await measureFunction(() => + getSearchOptions({ + options: optionLists, + betas: mockedBetas, + draftComments: {}, + nvpDismissedProductTraining, + loginList, + currentUserAccountID: MOCK_CURRENT_USER_ACCOUNT_ID, + currentUserEmail: MOCK_CURRENT_USER_EMAIL, + policyCollection: allPolicies, + personalDetails, + maxResults: 20, + }), + ); + }); }); From 694f12938f99c833240750b868d33356458b5447 Mon Sep 17 00:00:00 2001 From: VH Date: Fri, 6 Mar 2026 18:28:14 +0700 Subject: [PATCH 4/9] Run prettier --- src/pages/Share/ShareTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Share/ShareTab.tsx b/src/pages/Share/ShareTab.tsx index 583cb0b3eeee8..69c6211b1d933 100644 --- a/src/pages/Share/ShareTab.tsx +++ b/src/pages/Share/ShareTab.tsx @@ -2,13 +2,13 @@ import type {Ref} from 'react'; import React, {useEffect, useImperativeHandle, useRef} from 'react'; import {View} from 'react-native'; import {usePersonalDetails} from '@components/OnyxListItemProvider'; -import useFilteredOptions from '@hooks/useFilteredOptions'; import SelectionList from '@components/SelectionList'; import InviteMemberListItem from '@components/SelectionList/ListItem/InviteMemberListItem'; import type {ListItem, SelectionListHandle} from '@components/SelectionList/types'; import Text from '@components/Text'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebouncedState from '@hooks/useDebouncedState'; +import useFilteredOptions from '@hooks/useFilteredOptions'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; From 91e2db41be89a654eef75ed28c626757d5add3c4 Mon Sep 17 00:00:00 2001 From: VH Date: Sat, 7 Mar 2026 04:21:49 +0700 Subject: [PATCH 5/9] Replace standard sort with heap sort utility in createFilteredOptionList - Use optionsOrderBy (heap sort) instead of Array.sort for better efficiency - Add reportSortComparator to work with Report objects and privateIsArchivedMap - Heap sort is more efficient when limiting to top N reports --- src/libs/OptionsListUtils/index.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 371cdbcc2f0a0..191acb15dd26e 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -1464,6 +1464,15 @@ function createOptionList( }; } +/** + * Sort Report objects by archived status and last visible action + * Similar to recentReportComparator, but works with raw Report objects instead of SearchOptionData + */ +const reportSortComparator = (report: Report, privateIsArchivedMap: PrivateIsArchivedMap): string => { + const isArchived = !!privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`]; + return `${isArchived ? 0 : 1}_${report.lastVisibleActionCreated ?? ''}`; +}; + /** * Creates an optimized option list with smart pre-filtering. * @@ -1505,11 +1514,7 @@ function createFilteredOptionList( // In search mode, skip sorting because we return all reports anyway - sorting is unnecessary const sortedReports = isSearching ? reportsArray - : reportsArray.sort((a, b) => { - const aTime = new Date(a.lastVisibleActionCreated ?? 0).getTime(); - const bTime = new Date(b.lastVisibleActionCreated ?? 0).getTime(); - return bTime - aTime; - }); + : optionsOrderBy(reportsArray, (report) => reportSortComparator(report, privateIsArchivedMap), maxRecentReports); // Step 3: Limit to top N reports // In search mode, we will return all reports so downstream getSearchOptions will handle the actual term matching From 5dc280c83135284278ebd3e09c5be45d00747797 Mon Sep 17 00:00:00 2001 From: VH Date: Sat, 7 Mar 2026 04:25:44 +0700 Subject: [PATCH 6/9] Simplify report limiting logic in createFilteredOptionList Remove unnecessary effectiveLimit variable and use direct conditional expression for cleaner code --- src/libs/OptionsListUtils/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 191acb15dd26e..4e6eedc1bbe1c 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -1518,8 +1518,7 @@ function createFilteredOptionList( // Step 3: Limit to top N reports // In search mode, we will return all reports so downstream getSearchOptions will handle the actual term matching - const effectiveLimit = isSearching ? reportsArray.length : maxRecentReports; - const limitedReports = sortedReports.slice(0, effectiveLimit); + const limitedReports = isSearching ? sortedReports : sortedReports.slice(0, maxRecentReports); // Step 4: If search term is present, build report map with ONLY 1:1 DM reports // This allows personal details to have valid 1:1 DM reportIDs for proper avatar display From 4ca59d6b0cbb3142f9ea9e9bcae745a5ea714ef0 Mon Sep 17 00:00:00 2001 From: VH Date: Sat, 7 Mar 2026 05:35:24 +0700 Subject: [PATCH 7/9] Add test for isSearching behavior in createFilteredOptionList --- tests/unit/OptionsListUtilsTest.tsx | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 2f6d40170062b..6b43de76b0036 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -3759,23 +3759,6 @@ describe('OptionsListUtils', () => { // Report 2 should not have private_isArchived since it's not in the map expect(report2Option?.private_isArchived).toBeUndefined(); }); - - it('should respect maxRecentReports option while preserving archived status', () => { - renderLocaleContextProvider(); - // Given a privateIsArchivedMap and a small maxRecentReports limit - const privateIsArchivedMap: PrivateIsArchivedMap = { - [`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}7`]: '2023-12-31 23:59:59', // Report 7 has largest lastVisibleActionCreated - }; - - // When we call createFilteredOptionList with maxRecentReports limit - const result = createFilteredOptionList(PERSONAL_DETAILS, REPORTS, CURRENT_USER_ACCOUNT_ID, undefined, privateIsArchivedMap, { - maxRecentReports: 5, - }); - - // Then the report 7 (most recent) should still have private_isArchived set - const report7Option = result.reports.find((r) => r.item?.reportID === '7'); - expect(report7Option?.private_isArchived).toBe('2023-12-31 23:59:59'); - }); }); describe('filterSelfDMChat()', () => { @@ -6658,10 +6641,14 @@ describe('OptionsListUtils', () => { expect(result).toBeDefined(); }); - it('should handle searchTerm filtering', () => { - const result = createFilteredOptionList(PERSONAL_DETAILS, REPORTS, CURRENT_USER_ACCOUNT_ID, undefined, {}, {searchTerm: 'Spider'}); + it('should return all reports when searchTerm is provided (isSearching is true)', () => { + const result = createFilteredOptionList(PERSONAL_DETAILS, REPORTS, CURRENT_USER_ACCOUNT_ID, undefined, {}, { + searchTerm: 'Report', + maxRecentReports: 2, + }); expect(result).toBeDefined(); + expect(result.reports.length).toBe(Object.keys(REPORTS).length); }); it('should return both reports and personal details', () => { From 51f12c77a5c4d06331ee50ab6f1506ad2a94a2c6 Mon Sep 17 00:00:00 2001 From: VH Date: Sat, 7 Mar 2026 05:36:36 +0700 Subject: [PATCH 8/9] Run prettier --- src/libs/OptionsListUtils/index.ts | 4 +- tests/perf-test/OptionsListUtils.perf-test.ts | 37 ++++++------------- tests/unit/OptionsListUtilsTest.tsx | 15 ++++++-- 3 files changed, 23 insertions(+), 33 deletions(-) diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 4e6eedc1bbe1c..322e1fabd67bc 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -1512,9 +1512,7 @@ function createFilteredOptionList( // Step 2: Sort by lastVisibleActionCreated (most recent first) // In search mode, skip sorting because we return all reports anyway - sorting is unnecessary - const sortedReports = isSearching - ? reportsArray - : optionsOrderBy(reportsArray, (report) => reportSortComparator(report, privateIsArchivedMap), maxRecentReports); + const sortedReports = isSearching ? reportsArray : optionsOrderBy(reportsArray, (report) => reportSortComparator(report, privateIsArchivedMap), maxRecentReports); // Step 3: Limit to top N reports // In search mode, we will return all reports so downstream getSearchOptions will handle the actual term matching diff --git a/tests/perf-test/OptionsListUtils.perf-test.ts b/tests/perf-test/OptionsListUtils.perf-test.ts index 44f8a671aaaef..db6940c4e075d 100644 --- a/tests/perf-test/OptionsListUtils.perf-test.ts +++ b/tests/perf-test/OptionsListUtils.perf-test.ts @@ -3,7 +3,7 @@ import type * as NativeNavigation from '@react-navigation/native'; import Onyx from 'react-native-onyx'; import {measureFunction} from 'reassure'; import type {PrivateIsArchivedMap} from '@hooks/usePrivateIsArchivedMap'; -import {createOptionList, filterAndOrderOptions, getMemberInviteOptions, getSearchOptions, getValidOptions, createFilteredOptionList} from '@libs/OptionsListUtils'; +import {createFilteredOptionList, createOptionList, filterAndOrderOptions, getMemberInviteOptions, getSearchOptions, getValidOptions} from '@libs/OptionsListUtils'; import type {OptionData} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -289,41 +289,26 @@ describe('OptionsListUtils', () => { test('[OptionsListUtils] createFilteredOptionList', async () => { await waitForBatchedUpdates(); await measureFunction(() => - createFilteredOptionList( - personalDetails, - mockedReportsMap, - MOCK_CURRENT_USER_ACCOUNT_ID, - undefined, - EMPTY_PRIVATE_IS_ARCHIVED_MAP, - {maxRecentReports: 500, searchTerm: ''}, - ), + createFilteredOptionList(personalDetails, mockedReportsMap, MOCK_CURRENT_USER_ACCOUNT_ID, undefined, EMPTY_PRIVATE_IS_ARCHIVED_MAP, {maxRecentReports: 500, searchTerm: ''}), ); }); - test("[OptionsListUtils] createFilteredOptionList with searchTerm", async () => { + test('[OptionsListUtils] createFilteredOptionList with searchTerm', async () => { await waitForBatchedUpdates(); await measureFunction(() => - createFilteredOptionList( - personalDetails, - mockedReportsMap, - MOCK_CURRENT_USER_ACCOUNT_ID, - undefined, - EMPTY_PRIVATE_IS_ARCHIVED_MAP, - {maxRecentReports: 500,searchTerm: SEARCH_VALUE} - ), + createFilteredOptionList(personalDetails, mockedReportsMap, MOCK_CURRENT_USER_ACCOUNT_ID, undefined, EMPTY_PRIVATE_IS_ARCHIVED_MAP, { + maxRecentReports: 500, + searchTerm: SEARCH_VALUE, + }), ); }); test('[OptionsListUtils] getSearchOptions with searchTerm', async () => { await waitForBatchedUpdates(); - const optionLists = createFilteredOptionList( - personalDetails, - mockedReportsMap, - MOCK_CURRENT_USER_ACCOUNT_ID, - undefined, - EMPTY_PRIVATE_IS_ARCHIVED_MAP, - {maxRecentReports: 500,searchTerm: SEARCH_VALUE} - ); + const optionLists = createFilteredOptionList(personalDetails, mockedReportsMap, MOCK_CURRENT_USER_ACCOUNT_ID, undefined, EMPTY_PRIVATE_IS_ARCHIVED_MAP, { + maxRecentReports: 500, + searchTerm: SEARCH_VALUE, + }); await measureFunction(() => getSearchOptions({ diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 6b43de76b0036..fb3b14eba1b87 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -6642,10 +6642,17 @@ describe('OptionsListUtils', () => { }); it('should return all reports when searchTerm is provided (isSearching is true)', () => { - const result = createFilteredOptionList(PERSONAL_DETAILS, REPORTS, CURRENT_USER_ACCOUNT_ID, undefined, {}, { - searchTerm: 'Report', - maxRecentReports: 2, - }); + const result = createFilteredOptionList( + PERSONAL_DETAILS, + REPORTS, + CURRENT_USER_ACCOUNT_ID, + undefined, + {}, + { + searchTerm: 'Report', + maxRecentReports: 2, + }, + ); expect(result).toBeDefined(); expect(result.reports.length).toBe(Object.keys(REPORTS).length); From 9deb6dc37e46851a6b2976a50940a68d89b1abe2 Mon Sep 17 00:00:00 2001 From: VH Date: Sun, 8 Mar 2026 11:31:36 +0700 Subject: [PATCH 9/9] Remove redundant step 3 in createFilteredOptionList Since optionsOrderBy already accepts a limit parameter and efficiently returns only the top N items using a heap, the explicit slice operation was unnecessary. --- src/libs/OptionsListUtils/index.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 322e1fabd67bc..0c2e21dd9b269 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -1510,15 +1510,11 @@ function createFilteredOptionList( return !!report; }); - // Step 2: Sort by lastVisibleActionCreated (most recent first) + // Step 2: Sort by lastVisibleActionCreated (most recent first) and limit to top N // In search mode, skip sorting because we return all reports anyway - sorting is unnecessary const sortedReports = isSearching ? reportsArray : optionsOrderBy(reportsArray, (report) => reportSortComparator(report, privateIsArchivedMap), maxRecentReports); - // Step 3: Limit to top N reports - // In search mode, we will return all reports so downstream getSearchOptions will handle the actual term matching - const limitedReports = isSearching ? sortedReports : sortedReports.slice(0, maxRecentReports); - - // Step 4: If search term is present, build report map with ONLY 1:1 DM reports + // Step 3: If search term is present, build report map with ONLY 1:1 DM reports // This allows personal details to have valid 1:1 DM reportIDs for proper avatar display // Users without 1:1 DMs will have no report mapped, causing getIcons to fall back to personal avatar if (isSearching) { @@ -1544,9 +1540,9 @@ function createFilteredOptionList( } } - // Step 5: Process the limited set of reports (performance optimization) + // Step 4: Process the limited set of reports (performance optimization) const reportOptions: Array> = []; - for (const report of limitedReports) { + for (const report of sortedReports) { const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`]; const chatReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.chatReportID}`]; const {reportMapEntry, reportOption} = processReport(report, personalDetails, privateIsArchived, currentUserAccountID, chatReport, reportAttributesDerived, visibleReportActionsData); @@ -1572,7 +1568,7 @@ function createFilteredOptionList( } } - // Step 6: Process personal details (all of them - needed for search functionality) + // Step 5: Process personal details (all of them - needed for search functionality) const personalDetailsOptions = includeP2P ? Object.values(personalDetails ?? {}).map((personalDetail) => { const accountID = personalDetail?.accountID ?? CONST.DEFAULT_NUMBER_ID;