Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions src/components/Search/SearchAutocompleteList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type {ForwardedRef, RefObject} from 'react';
import React, {useContext, useEffect, useRef, useState} from 'react';
import React, {useEffect, useRef, useState} from 'react';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {OptionsListStateContext, useOptionsList} from '@components/OptionListContextProvider';
import OptionsListSkeletonView from '@components/OptionsListSkeletonView';
import type {AnimatedTextInputRef} from '@components/RNTextInput';
import type {ListItem as NewListItem, UserListItemProps} from '@components/SelectionList/ListItem/types';
Expand All @@ -15,6 +14,7 @@ import useAutocompleteSuggestions from '@hooks/useAutocompleteSuggestions';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDebounce from '@hooks/useDebounce';
import useFeedKeysWithAssignedCards from '@hooks/useFeedKeysWithAssignedCards';
import useFilteredOptions from '@hooks/useFilteredOptions';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand Down Expand Up @@ -93,6 +93,11 @@ const defaultListOptions = {
categoryOptions: [],
};

const emptyOptionList = {
reports: [],
personalDetails: [],
};

const setPerformanceTimersEnd = () => {
endSpan(CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER);
};
Expand Down Expand Up @@ -164,7 +169,7 @@ function SearchAutocompleteList({
const expensifyIcons = useMemoizedLazyExpensifyIcons(['History', 'MagnifyingGlass']);
const taxRates = getAllTaxRates(policies);

const {options, areOptionsInitialized} = useOptionsList();
const {options: listOptions, isLoading} = useFilteredOptions({enabled: true, isSearching: !!autocompleteQueryValue, betas: betas ?? []});

const isRecentSearchesDataLoaded = !isLoadingOnyxValue(recentSearchesMetadata);

Expand All @@ -174,25 +179,24 @@ function SearchAutocompleteList({
};
}, []);

const {areOptionsInitialized: contextAreOptionsInitialized} = useContext(OptionsListStateContext);
const coldStartAttributeSet = useRef(false);
useEffect(() => {
if (coldStartAttributeSet.current) {
return;
}
const parentSpan = getSpan(CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER);
if (parentSpan) {
parentSpan.setAttribute(CONST.TELEMETRY.ATTRIBUTE_COLD_START, !contextAreOptionsInitialized);
parentSpan.setAttribute(CONST.TELEMETRY.ATTRIBUTE_COLD_START, isLoading);
coldStartAttributeSet.current = true;
}
}, [contextAreOptionsInitialized]);
}, [isLoading]);

const searchOptions = (() => {
if (!areOptionsInitialized) {
if (listOptions === null) {
return defaultListOptions;
}
return getSearchOptions({
options,
options: listOptions,
draftComments,
nvpDismissedProductTraining,
betas: betas ?? [],
Expand Down Expand Up @@ -276,7 +280,7 @@ function SearchAutocompleteList({
autocompleteQueryValue,
allCards,
allFeeds,
options,
options: listOptions ?? emptyOptionList,
draftComments,
nvpDismissedProductTraining,
betas,
Expand Down Expand Up @@ -412,7 +416,7 @@ function SearchAutocompleteList({
// because useState(initialFocusedIndex) in useArrowKeyFocusManager only reads the initial value.
// Imperatively focus the first recent report once options become available (desktop only).
useEffect(() => {
if (shouldUseNarrowLayout || !areOptionsInitialized || hasSetInitialFocusRef.current || !firstRecentReportKey) {
if (shouldUseNarrowLayout || isLoading || hasSetInitialFocusRef.current || !firstRecentReportKey) {
return;
}
hasSetInitialFocusRef.current = true;
Expand All @@ -434,7 +438,7 @@ function SearchAutocompleteList({
flatIndex++;
}
}
}, [areOptionsInitialized, firstRecentReportKey, sections, shouldUseNarrowLayout]);
}, [isLoading, firstRecentReportKey, sections, shouldUseNarrowLayout]);

useEffect(() => {
const targetText = autocompleteQueryValue;
Expand All @@ -444,15 +448,15 @@ function SearchAutocompleteList({
}
}, [autocompleteQueryValue, onHighlightFirstItem, normalizedReferenceText]);

const isLoading = !isRecentSearchesDataLoaded || !areOptionsInitialized;
const isLoadingFullData = !isRecentSearchesDataLoaded || isLoading;

const reasonAttributes: SkeletonSpanReasonAttributes = {
context: 'SearchAutocompleteList',
isRecentSearchesDataLoaded,
areOptionsInitialized,
isLoading,
};

if (isLoading) {
if (isLoadingFullData) {
return (
<OptionsListSkeletonView
fixedNumItems={4}
Expand Down
10 changes: 5 additions & 5 deletions src/hooks/useFilteredOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ type UseFilteredOptionsConfig = {
batchSize?: number;
/** Whether to enable dynamic loading/pagination (default: true) */
enablePagination?: boolean;
/** Search term for filtering - when present, builds full report map for personal details (default: '') */
searchTerm?: string;
/** Whether search mode is active - when true, builds full report map for personal details (default: false) */
isSearching?: boolean;
/** Beta features the user has access to */
betas?: OnyxEntry<Beta[]>;
};
Expand Down Expand Up @@ -67,7 +67,7 @@ type UseFilteredOptionsResult = {
* />
*/
function useFilteredOptions(config: UseFilteredOptionsConfig = {}): UseFilteredOptionsResult {
const {maxRecentReports = 500, enabled = true, includeP2P = true, batchSize = 100, searchTerm = '', betas} = config;
const {maxRecentReports = 500, enabled = true, includeP2P = true, batchSize = 100, isSearching = false, betas} = config;

const [reportsLimit, setReportsLimit] = useState(maxRecentReports);

Expand All @@ -87,11 +87,11 @@ function useFilteredOptions(config: UseFilteredOptionsConfig = {}): UseFilteredO
? createFilteredOptionList(allPersonalDetails, allReports, currentUserPersonalDetails.accountID, reportAttributesDerived, privateIsArchivedMap, {
maxRecentReports: reportsLimit,
includeP2P,
searchTerm,
isSearching,
betas,
})
: null,
[enabled, allReports, allPersonalDetails, currentUserPersonalDetails.accountID, reportAttributesDerived, privateIsArchivedMap, reportsLimit, includeP2P, searchTerm, betas],
[enabled, allReports, allPersonalDetails, currentUserPersonalDetails.accountID, reportAttributesDerived, privateIsArchivedMap, reportsLimit, includeP2P, isSearching, betas],
);

const hasMore = options ? reportsLimit < totalReports : false;
Expand Down
5 changes: 2 additions & 3 deletions src/libs/OptionsListUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@
*/

let allReports: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 210 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function

Check warning on line 210 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -219,7 +219,7 @@
const allSortedReportActions: Record<string, ReportAction[]> = {};
const cachedOneTransactionThreadReportIDs: Record<string, string | undefined> = {};
let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 222 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function

Check warning on line 222 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand Down Expand Up @@ -264,7 +264,7 @@
});

let activePolicyID: OnyxEntry<string>;
Onyx.connect({

Check warning on line 267 in src/libs/OptionsListUtils/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NVP_ACTIVE_POLICY_ID,
callback: (value) => (activePolicyID = value),
});
Expand Down Expand Up @@ -1481,14 +1481,13 @@
options: {
maxRecentReports?: number;
includeP2P?: boolean;
searchTerm?: string;
isSearching?: boolean;
betas?: OnyxEntry<Beta[]>;
} = {},
policyTags?: OnyxCollection<PolicyTagLists>,
visibleReportActionsData: VisibleReportActionsDerivedValue = {},
) {
const {maxRecentReports = 500, includeP2P = true, searchTerm = ''} = options;
const isSearching = !!searchTerm?.trim();
const {maxRecentReports = 500, includeP2P = true, isSearching = false} = options;
const reportMapForAccountIDs: Record<number, Report> = {};

// Step 1: Pre-filter reports to avoid processing thousands
Expand Down
2 changes: 1 addition & 1 deletion src/pages/NewChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function useOptions(reportAttributesDerived: ReportAttributesDerivedValue['repor
includeP2P: true,
batchSize: 100,
enablePagination: true,
searchTerm: debouncedSearchTerm,
isSearching: !!debouncedSearchTerm,
Comment thread
hoangzinh marked this conversation as resolved.
Outdated
betas,
});

Expand Down
2 changes: 1 addition & 1 deletion src/pages/Share/ShareTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function ShareTab({ref}: ShareTabProps) {
const {options: listOptions, isLoading} = useFilteredOptions({
enabled: didScreenTransitionEnd,
betas: betas ?? [],
searchTerm: debouncedTextInputValue,
isSearching: !!debouncedTextInputValue,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hoangzinh Please trim the value here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated in this commit dbed4dc

});
const areOptionsInitialized = !isLoading;
const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false});
Expand Down
10 changes: 5 additions & 5 deletions tests/perf-test/OptionsListUtils.perf-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,25 +271,25 @@ 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, isSearching: false}),
);
});

test('[OptionsListUtils] createFilteredOptionList with searchTerm', async () => {
test('[OptionsListUtils] createFilteredOptionList with isSearching is true', async () => {
await waitForBatchedUpdates();
await measureFunction(() =>
createFilteredOptionList(personalDetails, mockedReportsMap, MOCK_CURRENT_USER_ACCOUNT_ID, undefined, EMPTY_PRIVATE_IS_ARCHIVED_MAP, {
maxRecentReports: 500,
searchTerm: SEARCH_VALUE,
isSearching: true,
}),
);
});

test('[OptionsListUtils] getSearchOptions with searchTerm', async () => {
test('[OptionsListUtils] getSearchOptions with isSearching is true', async () => {
await waitForBatchedUpdates();
const optionLists = createFilteredOptionList(personalDetails, mockedReportsMap, MOCK_CURRENT_USER_ACCOUNT_ID, undefined, EMPTY_PRIVATE_IS_ARCHIVED_MAP, {
maxRecentReports: 500,
searchTerm: SEARCH_VALUE,
isSearching: true,
});

await measureFunction(() =>
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/OptionsListUtilsTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6787,15 +6787,15 @@ describe('OptionsListUtils', () => {
expect(result).toBeDefined();
});

it('should return all reports when searchTerm is provided (isSearching is true)', () => {
it('should return all reports when isSearching is true', () => {
const result = createFilteredOptionList(
PERSONAL_DETAILS,
REPORTS,
CURRENT_USER_ACCOUNT_ID,
undefined,
{},
{
searchTerm: 'Report',
isSearching: true,
maxRecentReports: 2,
},
);
Expand Down
Loading