Skip to content

Commit 6d289e3

Browse files
authored
Merge pull request Expensify#75026 from mkzie2/mkzie2-issue/71061-revert
use useSearchSelector hook and remove unused and duplicated code
2 parents 7015231 + 48f3386 commit 6d289e3

4 files changed

Lines changed: 213 additions & 277 deletions

File tree

src/components/Search/FilterDropdowns/UserSelectPopup.tsx

Lines changed: 80 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import isEmpty from 'lodash/isEmpty';
2-
import React, {memo, useCallback, useMemo, useRef, useState} from 'react';
2+
import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react';
33
import {View} from 'react-native';
44
import Button from '@components/Button';
55
import {usePersonalDetails} from '@components/OnyxListItemProvider';
@@ -9,19 +9,16 @@ import type {ListItem, SelectionListHandle} from '@components/SelectionList/type
99
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
1010
import useLocalize from '@hooks/useLocalize';
1111
import useOnyx from '@hooks/useOnyx';
12-
import usePersonalDetailOptions from '@hooks/usePersonalDetailOptions';
1312
import useResponsiveLayout from '@hooks/useResponsiveLayout';
13+
import useSearchSelector from '@hooks/useSearchSelector';
1414
import useThemeStyles from '@hooks/useThemeStyles';
1515
import useWindowDimensions from '@hooks/useWindowDimensions';
1616
import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
17-
import memoize from '@libs/memoize';
18-
import {filterOption, getValidOptions} from '@libs/PersonalDetailOptionsListUtils';
19-
import type {OptionData} from '@libs/PersonalDetailOptionsListUtils';
17+
import {getParticipantsOption} from '@libs/OptionsListUtils';
18+
import type {OptionData} from '@libs/ReportUtils';
2019
import CONST from '@src/CONST';
2120
import ONYXKEYS from '@src/ONYXKEYS';
2221

23-
const memoizedGetValidOptions = memoize(getValidOptions, {maxSize: 5, monitoringName: 'UserSelectPopup.getValidOptions'});
24-
2522
type UserSelectPopupProps = {
2623
/** The currently selected users */
2724
value: string[];
@@ -43,75 +40,82 @@ type UserSelectPopupProps = {
4340
function UserSelectPopup({value, closeOverlay, onChange, isSearchable}: UserSelectPopupProps) {
4441
const selectionListRef = useRef<SelectionListHandle<ListItem> | null>(null);
4542
const styles = useThemeStyles();
46-
const {translate, formatPhoneNumber} = useLocalize();
47-
const {options, currentOption} = usePersonalDetailOptions();
43+
const {translate} = useLocalize();
4844
const personalDetails = usePersonalDetails();
4945
const {windowHeight} = useWindowDimensions();
5046
const {shouldUseNarrowLayout} = useResponsiveLayout();
5147
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
52-
const currentUserEmail = currentUserPersonalDetails.email ?? '';
48+
const currentUserAccountID = currentUserPersonalDetails.accountID;
5349
const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus();
54-
const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE);
55-
const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
56-
const [searchTerm, setSearchTerm] = useState('');
5750
const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false});
58-
59-
const getInitialSelectedIDs = useCallback(() => {
60-
return value.reduce<Set<string>>((acc, id) => {
51+
const initialSelectedOptions = useMemo(() => {
52+
return value.reduce<OptionData[]>((options, id) => {
6153
const participant = personalDetails?.[id];
6254
if (!participant) {
63-
return acc;
55+
return options;
6456
}
65-
acc.add(id);
66-
return acc;
67-
}, new Set<string>());
68-
}, [value, personalDetails]);
6957

70-
const [selectedAccountIDs, setSelectedAccountIDs] = useState<Set<string>>(() => getInitialSelectedIDs());
58+
const optionData = {
59+
...getParticipantsOption(participant, personalDetails),
60+
isSelected: true,
61+
};
7162

72-
const cleanSearchTerm = searchTerm.trim().toLowerCase();
63+
if (optionData) {
64+
options.push(optionData as OptionData);
65+
}
7366

74-
const transformedOptions = useMemo(
75-
() =>
76-
options?.map((option) => ({
77-
...option,
78-
isSelected: selectedAccountIDs.has(option.accountID.toString()),
79-
})) ?? [],
80-
[options, selectedAccountIDs],
81-
);
67+
return options;
68+
}, []);
69+
}, [value, personalDetails]);
8270

83-
const optionsList = useMemo(() => {
84-
return memoizedGetValidOptions(transformedOptions, currentUserEmail, formatPhoneNumber, countryCode, loginList, {
71+
const {searchTerm, debouncedSearchTerm, setSearchTerm, availableOptions, selectedOptions, toggleSelection, areOptionsInitialized, selectedOptionsForDisplay, onListEndReached} =
72+
useSearchSelector({
73+
selectionMode: CONST.SEARCH_SELECTOR.SELECTION_MODE_MULTI,
74+
searchContext: CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_GENERAL,
75+
initialSelected: initialSelectedOptions,
8576
excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT,
86-
includeCurrentUser: false,
87-
includeRecentReports: false,
88-
searchString: cleanSearchTerm,
77+
maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
78+
includeUserToInvite: false,
79+
includeCurrentUser: true,
8980
});
90-
}, [transformedOptions, currentUserEmail, cleanSearchTerm, formatPhoneNumber, countryCode, loginList]);
9181

92-
const currentUserSearchTerms = useMemo(() => [translate('common.you'), translate('common.me')], [translate]);
82+
const listData = useMemo(() => {
83+
const personalDetailsList = availableOptions.personalDetails.map((participant) => ({
84+
...participant,
85+
keyForList: String(participant.accountID),
86+
}));
87+
const recentReports = availableOptions.recentReports.map((report) => ({
88+
...report,
89+
keyForList: String(report.reportID),
90+
}));
91+
const combinedOptions = [...selectedOptionsForDisplay, ...personalDetailsList, ...recentReports];
92+
93+
// Sort the options so that selected items appear first, and the current user appears right after that, followed by the rest of the options in their original order
94+
combinedOptions.sort((a, b) => {
95+
// selected items first
96+
if (a.isSelected && !b.isSelected) {
97+
return -1;
98+
}
99+
if (!a.isSelected && b.isSelected) {
100+
return 1;
101+
}
93102

94-
const filteredCurrentUserOption = useMemo(() => {
95-
const newOption = filterOption(currentOption, cleanSearchTerm, currentUserSearchTerms);
96-
if (newOption) {
97-
return {
98-
...newOption,
99-
isSelected: selectedAccountIDs.has(newOption.accountID.toString()),
100-
};
101-
}
102-
return newOption;
103-
}, [currentOption, cleanSearchTerm, selectedAccountIDs, currentUserSearchTerms]);
103+
// Put the current user at the top of the list
104+
if (a.accountID === currentUserAccountID) {
105+
return -1;
106+
}
107+
if (b.accountID === currentUserAccountID) {
108+
return 1;
109+
}
110+
return 0;
111+
});
104112

105-
const listData = useMemo(() => {
106-
if (!filteredCurrentUserOption) {
107-
return [...optionsList.selectedOptions, ...optionsList.personalDetails];
108-
}
109-
const isCurrentOptionSelected = filteredCurrentUserOption.isSelected;
110-
if (isCurrentOptionSelected) {
111-
return [filteredCurrentUserOption, ...optionsList.selectedOptions, ...optionsList.personalDetails];
112-
}
113-
return [...optionsList.selectedOptions, filteredCurrentUserOption, ...optionsList.personalDetails];
114-
}, [filteredCurrentUserOption, optionsList.selectedOptions, optionsList.personalDetails]);
113+
const combinedOptionsWithKeyForList = combinedOptions.map((option) => ({
114+
...option,
115+
keyForList: option.keyForList ?? option.login ?? '',
116+
}));
117+
return combinedOptionsWithKeyForList;
118+
}, [availableOptions.personalDetails, availableOptions.recentReports, selectedOptionsForDisplay, currentUserAccountID]);
115119

116120
const headerMessage = useMemo(() => {
117121
const noResultsFound = isEmpty(listData);
@@ -120,40 +124,47 @@ function UserSelectPopup({value, closeOverlay, onChange, isSearchable}: UserSele
120124

121125
const selectUser = useCallback(
122126
(option: OptionData) => {
123-
const isSelected = selectedAccountIDs.has(option.accountID.toString());
124-
125-
setSelectedAccountIDs((prev) => (isSelected ? new Set([...prev].filter((id) => id !== option.accountID.toString())) : new Set([...prev, option.accountID.toString()])));
127+
toggleSelection(option);
126128
selectionListRef?.current?.scrollToIndex(0);
127129
},
128-
[selectedAccountIDs],
130+
[toggleSelection],
129131
);
130132

131133
const applyChanges = useCallback(() => {
132-
const accountIDs = Array.from(selectedAccountIDs);
134+
const accountIDs = selectedOptions.flatMap((option) => (option.accountID ? [option.accountID.toString()] : []));
133135
closeOverlay();
134136
onChange(accountIDs);
135-
}, [closeOverlay, onChange, selectedAccountIDs]);
137+
}, [closeOverlay, onChange, selectedOptions]);
136138

137139
const resetChanges = useCallback(() => {
138140
onChange([]);
139141
closeOverlay();
140142
}, [closeOverlay, onChange]);
141143

142144
const isLoadingNewOptions = !!isSearchingForReports;
143-
const shouldShowSearchInput = isSearchable ?? transformedOptions.length >= CONST.STANDARD_LIST_ITEM_LIMIT;
145+
const [totalOptionsCount, setTotalOptionsCount] = useState(() => selectedOptionsForDisplay.length + availableOptions.personalDetails.length + availableOptions.recentReports.length);
146+
147+
useEffect(() => {
148+
if (debouncedSearchTerm) {
149+
return;
150+
}
151+
setTotalOptionsCount(selectedOptionsForDisplay.length + availableOptions.personalDetails.length + availableOptions.recentReports.length);
152+
}, [debouncedSearchTerm, selectedOptionsForDisplay.length, availableOptions.personalDetails.length, availableOptions.recentReports.length]);
153+
154+
const shouldShowSearchInput = isSearchable ?? totalOptionsCount >= CONST.STANDARD_LIST_ITEM_LIMIT;
144155

145156
const textInputOptions = useMemo(
146157
() =>
147158
shouldShowSearchInput
148159
? {
149160
value: searchTerm,
150-
label: translate('common.search'),
161+
label: translate('selectionList.searchForSomeone'),
151162
onChangeText: setSearchTerm,
152163
headerMessage,
153164
disableAutoFocus: !shouldFocusInputOnScreenFocus,
154165
}
155166
: undefined,
156-
[searchTerm, translate, headerMessage, shouldFocusInputOnScreenFocus, shouldShowSearchInput],
167+
[shouldShowSearchInput, searchTerm, translate, setSearchTerm, headerMessage, shouldFocusInputOnScreenFocus],
157168
);
158169

159170
return (
@@ -167,6 +178,8 @@ function UserSelectPopup({value, closeOverlay, onChange, isSearchable}: UserSele
167178
style={{containerStyle: [!shouldUseNarrowLayout && styles.pt4], listStyle: styles.pb2}}
168179
onSelectRow={selectUser}
169180
isLoadingNewOptions={isLoadingNewOptions}
181+
shouldShowLoadingPlaceholder={!areOptionsInitialized}
182+
onEndReached={onListEndReached}
170183
/>
171184

172185
<View style={[styles.flexRow, styles.gap2, styles.mh5, !shouldUseNarrowLayout && styles.mb4]}>

0 commit comments

Comments
 (0)