Skip to content

Commit

Permalink
Merge pull request #54681 from margelo/perf/only-call-getSearchOption…
Browse files Browse the repository at this point in the history
…s-once

perf: only call expensive getSearchOptions once
  • Loading branch information
luacmartins authored Jan 13, 2025
2 parents c585eb9 + 7f83d60 commit 6a8125d
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 50 deletions.
100 changes: 53 additions & 47 deletions src/components/Search/SearchRouter/SearchRouter.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import {useNavigationState} from '@react-navigation/native';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
import type {TextInputProps} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import {useOptionsList} from '@components/OptionListContextProvider';
import type {AnimatedTextInputRef} from '@components/RNTextInput';
import type {SearchQueryString} from '@components/Search/types';
import {isSearchQueryItem} from '@components/SelectionList/Search/SearchQueryListItem';
Expand All @@ -19,7 +18,6 @@ import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import * as InputUtils from '@libs/InputUtils';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import type {SearchOption} from '@libs/OptionsListUtils';
import type {OptionData} from '@libs/ReportUtils';
import * as SearchAutocompleteUtils from '@libs/SearchAutocompleteUtils';
Expand All @@ -36,6 +34,7 @@ import {getQueryWithSubstitutions} from './getQueryWithSubstitutions';
import type {SubstitutionMap} from './getQueryWithSubstitutions';
import {getUpdatedSubstitutionsMap} from './getUpdatedSubstitutionsMap';
import SearchRouterInput from './SearchRouterInput';
import type {GetAdditionalSectionsCallback} from './SearchRouterList';
import SearchRouterList from './SearchRouterList';

function getContextualSearchAutocompleteKey(item: SearchQueryItem) {
Expand Down Expand Up @@ -75,7 +74,6 @@ type SearchRouterProps = {
function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [, recentSearchesMetadata] = useOnyx(ONYXKEYS.RECENT_SEARCHES);
const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false});
const {activeWorkspaceID} = useActiveWorkspace();
Expand All @@ -94,57 +92,65 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps)
return state?.routes.at(-1)?.params?.reportID;
});

const {options, areOptionsInitialized} = useOptionsList();
const searchOptions = useMemo(() => {
if (!areOptionsInitialized) {
return {recentReports: [], personalDetails: [], userToInvite: null, currentUserOption: null};
}
return OptionsListUtils.getSearchOptions(options, betas ?? []);
}, [areOptionsInitialized, betas, options]);
const getAdditionalSections: GetAdditionalSectionsCallback = useCallback(
({recentReports}) => {
if (!contextualReportID) {
return undefined;
}

// We will only show the contextual search suggestion if the user has not typed anything
if (textInputValue) {
return undefined;
}

const reportForContextualSearch = contextualReportID ? searchOptions.recentReports?.find((option) => option.reportID === contextualReportID) : undefined;
const reportForContextualSearch = recentReports.find((option) => option.reportID === contextualReportID);
if (!reportForContextualSearch) {
return undefined;
}

const additionalSections = [];
const reportQueryValue = reportForContextualSearch.text ?? reportForContextualSearch.alternateText ?? reportForContextualSearch.reportID;

if (reportForContextualSearch && !textInputValue) {
const reportQueryValue = reportForContextualSearch.text ?? reportForContextualSearch.alternateText ?? reportForContextualSearch.reportID;
let roomType: ValueOf<typeof CONST.SEARCH.DATA_TYPES> = CONST.SEARCH.DATA_TYPES.CHAT;
let autocompleteID: string | undefined = reportForContextualSearch.reportID;

let roomType: ValueOf<typeof CONST.SEARCH.DATA_TYPES> = CONST.SEARCH.DATA_TYPES.CHAT;
let autocompleteID = reportForContextualSearch.reportID;
if (reportForContextualSearch.isInvoiceRoom) {
roomType = CONST.SEARCH.DATA_TYPES.INVOICE;
const report = reportForContextualSearch as SearchOption<Report>;
if (report.item && report.item?.invoiceReceiver && report.item.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) {
autocompleteID = report.item.invoiceReceiver.accountID.toString();
} else {
autocompleteID = '';
if (reportForContextualSearch.isInvoiceRoom) {
roomType = CONST.SEARCH.DATA_TYPES.INVOICE;
const report = reportForContextualSearch as SearchOption<Report>;
if (report.item && report.item?.invoiceReceiver && report.item.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) {
autocompleteID = report.item.invoiceReceiver.accountID.toString();
} else {
autocompleteID = '';
}
}
}
if (reportForContextualSearch.isPolicyExpenseChat) {
roomType = CONST.SEARCH.DATA_TYPES.EXPENSE;
if (reportForContextualSearch.policyID) {
autocompleteID = reportForContextualSearch.policyID;
} else {
autocompleteID = '';
if (reportForContextualSearch.isPolicyExpenseChat) {
roomType = CONST.SEARCH.DATA_TYPES.EXPENSE;
if (reportForContextualSearch.policyID) {
autocompleteID = reportForContextualSearch.policyID;
} else {
autocompleteID = '';
}
}
}

additionalSections.push({
data: [
return [
{
text: `${translate('search.searchIn')} ${reportForContextualSearch.text ?? reportForContextualSearch.alternateText}`,
singleIcon: Expensicons.MagnifyingGlass,
searchQuery: reportQueryValue,
autocompleteID,
itemStyle: styles.activeComponentBG,
keyForList: 'contextualSearch',
searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.CONTEXTUAL_SUGGESTION,
roomType,
policyID: reportForContextualSearch.policyID,
data: [
{
text: `${translate('search.searchIn')} ${reportForContextualSearch.text ?? reportForContextualSearch.alternateText}`,
singleIcon: Expensicons.MagnifyingGlass,
searchQuery: reportQueryValue,
autocompleteID,
itemStyle: styles.activeComponentBG,
keyForList: 'contextualSearch',
searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.CONTEXTUAL_SUGGESTION,
roomType,
policyID: reportForContextualSearch.policyID,
},
],
},
],
});
}
];
},
[contextualReportID, styles.activeComponentBG, textInputValue, translate],
);

const searchQueryItem = textInputValue
? {
Expand Down Expand Up @@ -308,7 +314,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps)
<SearchRouterList
autocompleteQueryValue={autocompleteQueryValue || textInputValue}
searchQueryItem={searchQueryItem}
additionalSections={additionalSections}
getAdditionalSections={getAdditionalSections}
onListItemPress={onListItemPress}
setTextQuery={setTextInputValue}
updateAutocompleteSubstitutions={updateAutocompleteSubstitutions}
Expand Down
13 changes: 10 additions & 3 deletions src/components/Search/SearchRouter/SearchRouterList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,17 @@ type AutocompleteItemData = {
mapKey?: SearchFilterKey;
};

type GetAdditionalSectionsCallback = (options: OptionsListUtils.Options) => Array<SectionListDataType<OptionData | SearchQueryItem>> | undefined;

type SearchRouterListProps = {
/** Value of TextInput */
autocompleteQueryValue: string;

/** An optional item to always display on the top of the router list */
searchQueryItem?: SearchQueryItem;

/** Any extra sections that should be displayed in the router list */
additionalSections?: Array<SectionListDataType<OptionData | SearchQueryItem>>;
/** Any extra sections that should be displayed in the router list. */
getAdditionalSections?: GetAdditionalSectionsCallback;

/** Callback to call when an item is clicked/selected */
onListItemPress: (item: OptionData | SearchQueryItem) => void;
Expand Down Expand Up @@ -117,7 +119,7 @@ function SearchRouterItem(props: UserListItemProps<OptionData> | SearchQueryList

// Todo rename to SearchAutocompleteList once it's used in both Router and SearchPage
function SearchRouterList(
{autocompleteQueryValue, searchQueryItem, additionalSections, onListItemPress, setTextQuery, updateAutocompleteSubstitutions}: SearchRouterListProps,
{autocompleteQueryValue, searchQueryItem, getAdditionalSections, onListItemPress, setTextQuery, updateAutocompleteSubstitutions}: SearchRouterListProps,
ref: ForwardedRef<SelectionListHandle>,
) {
const styles = useThemeStyles();
Expand Down Expand Up @@ -419,6 +421,10 @@ function SearchRouterList(
sections.push({data: [searchQueryItem]});
}

const additionalSections = useMemo(() => {
return getAdditionalSections?.(searchOptions);
}, [getAdditionalSections, searchOptions]);

if (additionalSections) {
sections.push(...additionalSections);
}
Expand Down Expand Up @@ -485,3 +491,4 @@ function SearchRouterList(

export default forwardRef(SearchRouterList);
export {SearchRouterItem};
export type {GetAdditionalSectionsCallback};

0 comments on commit 6a8125d

Please sign in to comment.