From ab6ccaed81b5faa2360ce9d8f856cdd41f619391 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 19 Jan 2026 16:18:47 +0700 Subject: [PATCH 1/3] Refactor getValidOptions to use personalDetailsData from useOnyx --- .../FilterDropdowns/UserSelectPopup.tsx | 3 ++- .../SearchFiltersParticipantsSelector.tsx | 3 ++- src/hooks/useSearchSelector.base.ts | 9 +++++++ src/libs/OptionsListUtils/index.ts | 10 ++++++- src/libs/OptionsListUtils/types.ts | 3 ++- src/pages/NewChatPage.tsx | 3 +++ .../MoneyRequestAccountantSelector.tsx | 15 ++++++++++- tests/unit/OptionsListUtilsTest.tsx | 26 +++++++++++++++++++ 8 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/components/Search/FilterDropdowns/UserSelectPopup.tsx b/src/components/Search/FilterDropdowns/UserSelectPopup.tsx index e9fc33df0b20..e4d2b01f8813 100644 --- a/src/components/Search/FilterDropdowns/UserSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/UserSelectPopup.tsx @@ -103,10 +103,11 @@ function UserSelectPopup({value, closeOverlay, onChange, isSearchable}: UserSele { excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, includeCurrentUser: true, + personalDetails, }, countryCode, ); - }, [options.reports, options.personalDetails, draftComments, nvpDismissedProductTraining, loginList, countryCode]); + }, [options.reports, options.personalDetails, draftComments, nvpDismissedProductTraining, loginList, countryCode, personalDetails]); const filteredOptions = useMemo(() => { return filterAndOrderOptions(optionsList, cleanSearchTerm, countryCode, loginList, { diff --git a/src/components/Search/SearchFiltersParticipantsSelector.tsx b/src/components/Search/SearchFiltersParticipantsSelector.tsx index 9665142e3d08..77faf9739e41 100644 --- a/src/components/Search/SearchFiltersParticipantsSelector.tsx +++ b/src/components/Search/SearchFiltersParticipantsSelector.tsx @@ -72,10 +72,11 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate}: { excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, includeCurrentUser: true, + personalDetails, }, countryCode, ); - }, [areOptionsInitialized, options.reports, options.personalDetails, draftComments, nvpDismissedProductTraining, loginList, countryCode]); + }, [areOptionsInitialized, options.reports, options.personalDetails, draftComments, nvpDismissedProductTraining, loginList, countryCode, personalDetails]); const unselectedOptions = useMemo(() => { return filterSelectedOptions(defaultOptions, new Set(selectedOptions.map((option) => option.accountID))); diff --git a/src/hooks/useSearchSelector.base.ts b/src/hooks/useSearchSelector.base.ts index cd87176342b2..3787db4358ce 100644 --- a/src/hooks/useSearchSelector.base.ts +++ b/src/hooks/useSearchSelector.base.ts @@ -1,5 +1,6 @@ import {useCallback, useMemo, useState} from 'react'; import type {PermissionStatus} from 'react-native-permissions'; +import {usePersonalDetails} from '@components/OnyxListItemProvider'; import {useOptionsList} from '@components/OptionListContextProvider'; import type {GetOptionsConfig, Options, SearchOption} from '@libs/OptionsListUtils'; import {getEmptyOptions, getSearchOptions, getSearchValueForPhoneOrEmail, getValidOptions} from '@libs/OptionsListUtils'; @@ -164,6 +165,7 @@ function useSearchSelectorBase({ const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); + const personalDetails = usePersonalDetails(); const onListEndReached = useDebounce( useCallback(() => { @@ -195,6 +197,7 @@ function useSearchSelectorBase({ includeUserToInvite, countryCode, loginList, + personalDetails, }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_MEMBER_INVITE: return getValidOptions(optionsWithContacts, draftComments, nvpDismissedProductTraining, loginList, { @@ -207,6 +210,7 @@ function useSearchSelectorBase({ maxRecentReportElements: maxRecentReportsToShow, searchString: computedSearchTerm, includeUserToInvite, + personalDetails, }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_GENERAL: return getValidOptions(optionsWithContacts, draftComments, nvpDismissedProductTraining, loginList, { @@ -217,6 +221,7 @@ function useSearchSelectorBase({ maxRecentReportElements: maxRecentReportsToShow, includeUserToInvite, excludeLogins, + personalDetails, }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_SHARE_LOG: return getValidOptions( @@ -236,6 +241,7 @@ function useSearchSelectorBase({ searchString: computedSearchTerm, maxElements: maxResults, includeUserToInvite, + personalDetails, }, countryCode, ); @@ -256,6 +262,7 @@ function useSearchSelectorBase({ searchString: computedSearchTerm, maxElements: maxResults, includeUserToInvite, + personalDetails, }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_ATTENDEES: return getValidOptions(optionsWithContacts, draftComments, nvpDismissedProductTraining, loginList, { @@ -272,6 +279,7 @@ function useSearchSelectorBase({ includeUserToInvite, includeCurrentUser, shouldAcceptName: true, + personalDetails, }); default: return getEmptyOptions(); @@ -294,6 +302,7 @@ function useSearchSelectorBase({ getValidOptionsConfig, selectedOptions, includeCurrentUser, + personalDetails, ]); const isOptionSelected = useMemo(() => { diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 0ecf0307e652..f5de2224602e 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -1999,6 +1999,7 @@ function prepareReportOptionsForDisplay(options: Array>, co showRBR = true, shouldShowGBR = false, shouldUnreadBeBold = false, + personalDetails, } = config; const validOptions: Array> = []; @@ -2069,7 +2070,7 @@ function prepareReportOptionsForDisplay(options: Array>, co if (report?.policyID) { const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`]; const submitToAccountID = getSubmitToAccountID(policy, report); - const submitsToAccountDetails = allPersonalDetails?.[submitToAccountID]; + const submitsToAccountDetails = (personalDetails ?? allPersonalDetails)?.[submitToAccountID]; const subtitle = submitsToAccountDetails?.displayName ?? submitsToAccountDetails?.login; if (subtitle) { @@ -2147,6 +2148,7 @@ function getValidOptions( includeUserToInvite = false, maxRecentReportElements = undefined, shouldAcceptName = false, + personalDetails, ...config }: GetOptionsConfig = {}, countryCode: number = CONST.DEFAULT_COUNTRY_CODE, @@ -2227,6 +2229,7 @@ function getValidOptions( shouldSeparateSelfDMChat, shouldSeparateWorkspaceChat, shouldShowGBR, + personalDetails, }).at(0); } @@ -2240,6 +2243,7 @@ function getValidOptions( shouldSeparateSelfDMChat, shouldSeparateWorkspaceChat, shouldShowGBR, + personalDetails, }); workspaceChats = prepareReportOptionsForDisplay(workspaceChats, { @@ -2249,6 +2253,7 @@ function getValidOptions( shouldSeparateSelfDMChat, shouldSeparateWorkspaceChat, shouldShowGBR, + personalDetails, }); } else if (recentAttendees && recentAttendees?.length > 0) { recentAttendees.filter((attendee) => { @@ -2364,6 +2369,7 @@ type SearchOptionsConfig = { shouldShowGBR?: boolean; shouldUnreadBeBold?: boolean; loginList: OnyxEntry; + personalDetails?: OnyxEntry; }; /** @@ -2385,6 +2391,7 @@ function getSearchOptions({ shouldShowGBR = false, shouldUnreadBeBold = false, loginList, + personalDetails, }: SearchOptionsConfig): Options { Timing.start(CONST.TIMING.LOAD_SEARCH_OPTIONS); Performance.markStart(CONST.TIMING.LOAD_SEARCH_OPTIONS); @@ -2414,6 +2421,7 @@ function getSearchOptions({ includeUserToInvite, shouldShowGBR, shouldUnreadBeBold, + personalDetails, }, countryCode, ); diff --git a/src/libs/OptionsListUtils/types.ts b/src/libs/OptionsListUtils/types.ts index fb2b73e3f30e..a42669e1dde2 100644 --- a/src/libs/OptionsListUtils/types.ts +++ b/src/libs/OptionsListUtils/types.ts @@ -2,7 +2,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {OptionData} from '@libs/ReportUtils'; import type {AvatarSource} from '@libs/UserAvatarUtils'; import type {IOUAction} from '@src/CONST'; -import type {Beta, Login, PersonalDetails, Report, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type {Beta, Login, PersonalDetails, PersonalDetailsList, Report, ReportActions, TransactionViolation} from '@src/types/onyx'; import type {Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; /** @@ -157,6 +157,7 @@ type GetValidReportsConfig = { preferredPolicyID?: string; shouldUnreadBeBold?: boolean; shouldAlwaysIncludeDM?: boolean; + personalDetails?: OnyxEntry; } & GetValidOptionsSharedConfig; type IsValidReportsConfig = Pick< diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 2f1915763819..dafc4564864d 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -6,6 +6,7 @@ import type {Ref} from 'react'; import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {Keyboard} from 'react-native'; import Button from '@components/Button'; +import {usePersonalDetails} from '@components/OnyxListItemProvider'; import {PressableWithFeedback} from '@components/Pressable'; import ReferralProgramCTA from '@components/ReferralProgramCTA'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -67,6 +68,7 @@ function useOptions() { const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); const {contacts} = useContactImport(); const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); + const allPersonalDetails = usePersonalDetails(); const { options: listOptions, @@ -101,6 +103,7 @@ function useOptions() { betas: betas ?? [], includeSelfDM: true, shouldAlwaysIncludeDM: true, + personalDetails: allPersonalDetails, }, countryCode, ); diff --git a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx index cbcbe5739374..68525a432d32 100644 --- a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx @@ -88,6 +88,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType betas, excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, action, + personalDetails, }, countryCode, ); @@ -98,7 +99,19 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType ...optionList, ...orderedOptions, }; - }, [areOptionsInitialized, didScreenTransitionEnd, options.reports, options.personalDetails, draftComments, nvpDismissedProductTraining, loginList, betas, action, countryCode]); + }, [ + areOptionsInitialized, + didScreenTransitionEnd, + options.reports, + options.personalDetails, + draftComments, + nvpDismissedProductTraining, + loginList, + betas, + action, + countryCode, + personalDetails, + ]); const chatOptions = useMemo(() => { if (!areOptionsInitialized) { diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 679a3a3f462c..a1ca986cfd71 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -1112,6 +1112,32 @@ describe('OptionsListUtils', () => { expect(results.recentReports.at(0)?.isBold).toBe(true); expect(results.recentReports.at(0)?.isUnread).toBe(true); }); + + it('should use personalDetails parameter when passed to getValidOptions', () => { + // Given a personalDetails object to pass explicitly + const customPersonalDetails = { + 2: { + accountID: 2, + displayName: 'Custom Iron Man', + login: 'tonystark@expensify.com', + }, + 3: { + accountID: 3, + displayName: 'Custom Spider-Man', + login: 'peterparker@expensify.com', + }, + }; + + // When we call getValidOptions with personalDetails parameter + const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { + personalDetails: customPersonalDetails, + }); + + // Then the function should complete without errors and return valid results + // The personalDetails param is used internally by prepareReportOptionsForDisplay for workspace chats + expect(results.recentReports.length).toBeGreaterThan(0); + expect(results.personalDetails.length).toBeGreaterThan(0); + }); }); describe('getValidOptions() for chat room', () => { From 91c2e376c70b0f5372f891b5f235413b948e3121 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 19 Jan 2026 16:25:28 +0700 Subject: [PATCH 2/3] remove unused code --- src/hooks/useSearchSelector.base.ts | 1 - src/libs/OptionsListUtils/index.ts | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/hooks/useSearchSelector.base.ts b/src/hooks/useSearchSelector.base.ts index 3787db4358ce..ecc93229bf45 100644 --- a/src/hooks/useSearchSelector.base.ts +++ b/src/hooks/useSearchSelector.base.ts @@ -197,7 +197,6 @@ function useSearchSelectorBase({ includeUserToInvite, countryCode, loginList, - personalDetails, }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_MEMBER_INVITE: return getValidOptions(optionsWithContacts, draftComments, nvpDismissedProductTraining, loginList, { diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index f5de2224602e..66a02b41acd1 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -2391,7 +2391,6 @@ function getSearchOptions({ shouldShowGBR = false, shouldUnreadBeBold = false, loginList, - personalDetails, }: SearchOptionsConfig): Options { Timing.start(CONST.TIMING.LOAD_SEARCH_OPTIONS); Performance.markStart(CONST.TIMING.LOAD_SEARCH_OPTIONS); @@ -2421,7 +2420,6 @@ function getSearchOptions({ includeUserToInvite, shouldShowGBR, shouldUnreadBeBold, - personalDetails, }, countryCode, ); From 4fb48da3ef4d0fc05cd9d58b1047cc0e6eb48827 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 20 Jan 2026 11:52:34 +0700 Subject: [PATCH 3/3] update test --- tests/unit/OptionsListUtilsTest.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index c977b10a39ad..dfb3d6ceb9ab 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -1155,7 +1155,7 @@ describe('OptionsListUtils', () => { }; // When we call getValidOptions with personalDetails parameter - const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { + const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {}, nvpDismissedProductTraining, loginList, { personalDetails: customPersonalDetails, });