From 6a25950eb5f4148d53e948b72c085b9bd388cbf3 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Tue, 10 Mar 2026 03:58:57 +0500 Subject: [PATCH 1/5] 82585: Web - Search - Search list shows blank recent searches --- .../Search/SearchAutocompleteList.tsx | 103 ++++++++++-------- 1 file changed, 56 insertions(+), 47 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 615f27e05d4e2..b5b937bd58950 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -1,5 +1,5 @@ import type {ForwardedRef, RefObject} from 'react'; -import React, {useContext, useEffect, useRef, useState} from 'react'; +import React, {useContext, useEffect, useMemo, useRef, useState} from 'react'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {OptionsListStateContext, useOptionsList} from '@components/OptionListContextProvider'; import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; @@ -297,9 +297,9 @@ function SearchAutocompleteList({ const autocompleteQueryWithoutFilters = getQueryWithoutFilters(autocompleteQueryValue); - const sortedRecentSearches = Object.values(recentSearches ?? {}).sort((a, b) => localeCompare(b.timestamp, a.timestamp)); + const sortedRecentSearches = Object.entries(recentSearches ?? {}).sort(([, firstRecentSearch], [, secondRecentSearch]) => localeCompare(secondRecentSearch.timestamp, firstRecentSearch.timestamp)); - const recentSearchesData = sortedRecentSearches?.slice(0, 5).map(({query, timestamp}) => { + const recentSearchesData = sortedRecentSearches?.slice(0, 5).map(([recentSearchHash, {query}]) => { const searchQueryJSON = buildSearchQueryJSON(query); return { text: searchQueryJSON @@ -319,7 +319,7 @@ function SearchAutocompleteList({ : query, singleIcon: expensifyIcons.History, searchQuery: query, - keyForList: timestamp, + keyForList: recentSearchHash, searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.SEARCH, }; }); @@ -354,57 +354,66 @@ function SearchAutocompleteList({ debounceHandleSearch(); }, [autocompleteQueryWithoutFilters, debounceHandleSearch]); + const styledRecentReports = useMemo( + () => + recentReportsOptions.map((option) => { + const report = getReportOrDraftReport(option.reportID); + const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); + const shouldParserToHTML = reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT; + const keyForList = option.keyForList ?? option.reportID ?? (option.accountID ? String(option.accountID) : undefined); + return { + ...option, + keyForList, + pressableStyle: styles.br2, + text: StringUtils.lineBreaksToSpaces(shouldParserToHTML ? Parser.htmlToText(option.text ?? '') : (option.text ?? '')), + wrapperStyle: [styles.pr3, styles.pl3], + } as AutocompleteListItem; + }), + [recentReportsOptions, styles.br2, styles.pl3, styles.pr3], + ); + /* Sections generation */ - const sections: Array> = []; - let sectionIndex = 0; + const sections: Array> = useMemo(() => { + const generatedSections: Array> = []; + let sectionIndex = 0; - if (searchQueryItem) { - sections.push({data: [searchQueryItem as AutocompleteListItem], sectionIndex: sectionIndex++}); - } + if (searchQueryItem) { + generatedSections.push({data: [searchQueryItem as AutocompleteListItem], sectionIndex: sectionIndex++}); + } - const additionalSections = getAdditionalSections?.(searchOptions, sectionIndex); + const additionalSections = getAdditionalSections?.(searchOptions, sectionIndex); - if (additionalSections) { - for (const section of additionalSections) { - sections.push(section); - sectionIndex++; + if (additionalSections) { + for (const section of additionalSections) { + generatedSections.push(section); + sectionIndex++; + } } - } - if (!autocompleteQueryValue && recentSearchesData && recentSearchesData.length > 0) { - sections.push({title: translate('search.recentSearches'), data: recentSearchesData as AutocompleteListItem[], sectionIndex: sectionIndex++}); - } - const styledRecentReports = recentReportsOptions.map((option) => { - const report = getReportOrDraftReport(option.reportID); - const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); - const shouldParserToHTML = reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT; - const keyForList = option.keyForList ?? option.reportID ?? (option.accountID ? String(option.accountID) : undefined); - return { - ...option, - keyForList, - pressableStyle: styles.br2, - text: StringUtils.lineBreaksToSpaces(shouldParserToHTML ? Parser.htmlToText(option.text ?? '') : (option.text ?? '')), - wrapperStyle: [styles.pr3, styles.pl3], - } as AutocompleteListItem; - }); + if (!autocompleteQueryValue && recentSearchesData && recentSearchesData.length > 0) { + generatedSections.push({title: translate('search.recentSearches'), data: recentSearchesData as AutocompleteListItem[], sectionIndex: sectionIndex++}); + } - sections.push({title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined, data: styledRecentReports, sectionIndex: sectionIndex++}); - - if (autocompleteSuggestions.length > 0) { - const autocompleteData: AutocompleteListItem[] = autocompleteSuggestions.map(({filterKey, text, autocompleteID, mapKey}) => { - return { - text: getAutocompleteDisplayText(filterKey, text), - mapKey: mapKey ? getSubstitutionMapKey(mapKey, text) : undefined, - singleIcon: expensifyIcons.MagnifyingGlass, - searchQuery: text, - autocompleteID, - keyForList: autocompleteID ?? text, // in case we have a unique identifier then use it because text might not be unique - searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION, - }; - }); + generatedSections.push({title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined, data: styledRecentReports, sectionIndex: sectionIndex++}); + + if (autocompleteSuggestions.length > 0) { + const autocompleteData: AutocompleteListItem[] = autocompleteSuggestions.map(({filterKey, text, autocompleteID, mapKey}) => { + return { + text: getAutocompleteDisplayText(filterKey, text), + mapKey: mapKey ? getSubstitutionMapKey(mapKey, text) : undefined, + singleIcon: expensifyIcons.MagnifyingGlass, + searchQuery: text, + autocompleteID, + keyForList: autocompleteID ?? text, // in case we have a unique identifier then use it because text might not be unique + searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION, + }; + }); + + generatedSections.push({title: translate('search.suggestions'), data: autocompleteData, sectionIndex: sectionIndex++}); + } - sections.push({title: translate('search.suggestions'), data: autocompleteData, sectionIndex: sectionIndex++}); - } + return generatedSections; + }, [autocompleteQueryValue, autocompleteSuggestions, expensifyIcons.MagnifyingGlass, getAdditionalSections, recentSearchesData, searchOptions, searchQueryItem, styledRecentReports, translate]); const sectionItemText = sections?.at(1)?.data?.[0]?.text ?? ''; const normalizedReferenceText = sectionItemText.toLowerCase(); From 424b1d497bb22db3214b427fff096c9489021996 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Tue, 10 Mar 2026 04:09:16 +0500 Subject: [PATCH 2/5] Fixed pretter issue --- src/components/Search/SearchAutocompleteList.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index b5b937bd58950..7b71a781ca434 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -297,7 +297,9 @@ function SearchAutocompleteList({ const autocompleteQueryWithoutFilters = getQueryWithoutFilters(autocompleteQueryValue); - const sortedRecentSearches = Object.entries(recentSearches ?? {}).sort(([, firstRecentSearch], [, secondRecentSearch]) => localeCompare(secondRecentSearch.timestamp, firstRecentSearch.timestamp)); + const sortedRecentSearches = Object.entries(recentSearches ?? {}).sort(([, firstRecentSearch], [, secondRecentSearch]) => + localeCompare(secondRecentSearch.timestamp, firstRecentSearch.timestamp), + ); const recentSearchesData = sortedRecentSearches?.slice(0, 5).map(([recentSearchHash, {query}]) => { const searchQueryJSON = buildSearchQueryJSON(query); @@ -413,7 +415,17 @@ function SearchAutocompleteList({ } return generatedSections; - }, [autocompleteQueryValue, autocompleteSuggestions, expensifyIcons.MagnifyingGlass, getAdditionalSections, recentSearchesData, searchOptions, searchQueryItem, styledRecentReports, translate]); + }, [ + autocompleteQueryValue, + autocompleteSuggestions, + expensifyIcons.MagnifyingGlass, + getAdditionalSections, + recentSearchesData, + searchOptions, + searchQueryItem, + styledRecentReports, + translate, + ]); const sectionItemText = sections?.at(1)?.data?.[0]?.text ?? ''; const normalizedReferenceText = sectionItemText.toLowerCase(); From 61b5844b11c26367fab145af5250407e9cae82e9 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Sat, 14 Mar 2026 04:36:30 +0500 Subject: [PATCH 3/5] Fixed github actions feedback --- .../Search/SearchAutocompleteList.tsx | 113 ++++++++---------- 1 file changed, 48 insertions(+), 65 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index f0c86e30f5236..2aaf82a61af49 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -1,5 +1,5 @@ import type {ForwardedRef, RefObject} from 'react'; -import React, {useContext, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useContext, useEffect, useRef, useState} from 'react'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {OptionsListStateContext, useOptionsList} from '@components/OptionListContextProvider'; import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; @@ -218,6 +218,7 @@ function SearchAutocompleteList({ const prevQueryRef = useRef(autocompleteQueryValue); const innerListRef = useRef(null); const hasSetInitialFocusRef = useRef(false); + const sectionsRef = useRef>>([]); // Callback ref to set both inner ref and forward to external ref const setListRef = (instance: SelectionListWithSectionsHandle | null) => { @@ -351,76 +352,58 @@ function SearchAutocompleteList({ debounceHandleSearch(); }, [autocompleteQueryWithoutFilters, debounceHandleSearch]); - const styledRecentReports = useMemo( - () => - recentReportsOptions.map((option) => { - const report = getReportOrDraftReport(option.reportID); - const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); - const shouldParserToHTML = reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT; - const keyForList = option.keyForList ?? option.reportID ?? (option.accountID ? String(option.accountID) : undefined); - return { - ...option, - keyForList, - pressableStyle: styles.br2, - text: StringUtils.lineBreaksToSpaces(shouldParserToHTML ? Parser.htmlToText(option.text ?? '') : (option.text ?? '')), - wrapperStyle: [styles.pr3, styles.pl3], - } as AutocompleteListItem; - }), - [recentReportsOptions, styles.br2, styles.pl3, styles.pr3], - ); + const styledRecentReports = recentReportsOptions.map((option) => { + const report = getReportOrDraftReport(option.reportID); + const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); + const shouldParserToHTML = reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT; + const keyForList = option.keyForList ?? option.reportID ?? (option.accountID ? String(option.accountID) : undefined); + return { + ...option, + keyForList, + pressableStyle: styles.br2, + text: StringUtils.lineBreaksToSpaces(shouldParserToHTML ? Parser.htmlToText(option.text ?? '') : (option.text ?? '')), + wrapperStyle: [styles.pr3, styles.pl3], + } as AutocompleteListItem; + }); /* Sections generation */ - const sections: Array> = useMemo(() => { - const generatedSections: Array> = []; - let sectionIndex = 0; - - if (searchQueryItem) { - generatedSections.push({data: [searchQueryItem as AutocompleteListItem], sectionIndex: sectionIndex++}); - } + const sections: Array> = []; + let sectionIndex = 0; - const additionalSections = getAdditionalSections?.(searchOptions, sectionIndex); + if (searchQueryItem) { + sections.push({data: [searchQueryItem as AutocompleteListItem], sectionIndex: sectionIndex++}); + } - if (additionalSections) { - for (const section of additionalSections) { - generatedSections.push(section); - sectionIndex++; - } + const additionalSections = getAdditionalSections?.(searchOptions, sectionIndex); + if (additionalSections) { + for (const section of additionalSections) { + sections.push(section); + sectionIndex++; } + } - if (!autocompleteQueryValue && recentSearchesData && recentSearchesData.length > 0) { - generatedSections.push({title: translate('search.recentSearches'), data: recentSearchesData as AutocompleteListItem[], sectionIndex: sectionIndex++}); - } + if (!autocompleteQueryValue && recentSearchesData && recentSearchesData.length > 0) { + sections.push({title: translate('search.recentSearches'), data: recentSearchesData as AutocompleteListItem[], sectionIndex: sectionIndex++}); + } - generatedSections.push({title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined, data: styledRecentReports, sectionIndex: sectionIndex++}); - - if (autocompleteSuggestions.length > 0) { - const autocompleteData: AutocompleteListItem[] = autocompleteSuggestions.map(({filterKey, text, autocompleteID, mapKey}) => { - return { - text: getAutocompleteDisplayText(filterKey, text), - mapKey: mapKey ? getSubstitutionMapKey(mapKey, text) : undefined, - singleIcon: expensifyIcons.MagnifyingGlass, - searchQuery: text, - autocompleteID, - keyForList: autocompleteID ?? text, // in case we have a unique identifier then use it because text might not be unique - searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION, - }; - }); - - generatedSections.push({title: translate('search.suggestions'), data: autocompleteData, sectionIndex: sectionIndex++}); - } + sections.push({title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined, data: styledRecentReports, sectionIndex: sectionIndex++}); + + if (autocompleteSuggestions.length > 0) { + const autocompleteData: AutocompleteListItem[] = autocompleteSuggestions.map(({filterKey, text, autocompleteID, mapKey}) => { + return { + text: getAutocompleteDisplayText(filterKey, text), + mapKey: mapKey ? getSubstitutionMapKey(mapKey, text) : undefined, + singleIcon: expensifyIcons.MagnifyingGlass, + searchQuery: text, + autocompleteID, + keyForList: autocompleteID ?? text, // in case we have a unique identifier then use it because text might not be unique + searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION, + }; + }); - return generatedSections; - }, [ - autocompleteQueryValue, - autocompleteSuggestions, - expensifyIcons.MagnifyingGlass, - getAdditionalSections, - recentSearchesData, - searchOptions, - searchQueryItem, - styledRecentReports, - translate, - ]); + sections.push({title: translate('search.suggestions'), data: autocompleteData, sectionIndex: sectionIndex++}); + } + sectionsRef.current = sections; const sectionItemText = sections?.at(1)?.data?.[0]?.text ?? ''; const normalizedReferenceText = sectionItemText.toLowerCase(); @@ -439,7 +422,7 @@ function SearchAutocompleteList({ // Compute the flat index of firstRecentReportKey by replicating the flattening logic // from useFlattenedSections: each section may prepend a header row when it has a title/customHeader. let flatIndex = 0; - for (const section of sections) { + for (const section of sectionsRef.current) { const hasData = (section.data?.length ?? 0) > 0; const hasHeader = hasData && (section.title !== undefined || ('customHeader' in section && section.customHeader !== undefined)); if (hasHeader) { @@ -453,7 +436,7 @@ function SearchAutocompleteList({ flatIndex++; } } - }, [areOptionsInitialized, firstRecentReportKey, sections, shouldUseNarrowLayout]); + }, [areOptionsInitialized, firstRecentReportKey, shouldUseNarrowLayout]); useEffect(() => { const targetText = autocompleteQueryValue; From 4f2388ba7e55951b4edfd1e3a9c7896a4e00e938 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Wed, 18 Mar 2026 03:09:32 +0500 Subject: [PATCH 4/5] fixed glitch issue --- .../Search/SearchAutocompleteList.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 2aaf82a61af49..ba8f7ad986897 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -386,7 +386,22 @@ function SearchAutocompleteList({ sections.push({title: translate('search.recentSearches'), data: recentSearchesData as AutocompleteListItem[], sectionIndex: sectionIndex++}); } - sections.push({title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined, data: styledRecentReports, sectionIndex: sectionIndex++}); + if (areOptionsInitialized) { + sections.push({title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined, data: styledRecentReports, sectionIndex: sectionIndex++}); + } else if (autocompleteQueryValue.trim() === '') { + sections.push({ + title: translate('search.recentChats'), + data: [], + sectionIndex: sectionIndex++, + customHeader: ( + + ), + }); + } if (autocompleteSuggestions.length > 0) { const autocompleteData: AutocompleteListItem[] = autocompleteSuggestions.map(({filterKey, text, autocompleteID, mapKey}) => { @@ -446,7 +461,7 @@ function SearchAutocompleteList({ } }, [autocompleteQueryValue, onHighlightFirstItem, normalizedReferenceText]); - const isLoading = !isRecentSearchesDataLoaded || !areOptionsInitialized; + const isLoading = !isRecentSearchesDataLoaded; const reasonAttributes: SkeletonSpanReasonAttributes = { context: 'SearchAutocompleteList', From 515f0215c7b8490b458a67a05176b0e9361ce25f Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Fri, 27 Mar 2026 02:50:02 +0500 Subject: [PATCH 5/5] Merged conflicts --- .../Search/SearchAutocompleteList.tsx | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index de179d9ef22b5..2af5a351b24f8 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -354,6 +354,12 @@ function SearchAutocompleteList({ debounceHandleSearch(); }, [autocompleteQueryWithoutFilters, debounceHandleSearch]); + const reasonAttributes: SkeletonSpanReasonAttributes = { + context: 'SearchAutocompleteList', + isRecentSearchesDataLoaded, + areOptionsInitialized, + }; + /* Sections generation */ const {sections, styledRecentReports, suggestionsCount} = useMemo(() => { const nextSections: Array> = []; @@ -412,6 +418,11 @@ function SearchAutocompleteList({ fixedNumItems={3} shouldStyleAsTable speed={CONST.TIMING.SKELETON_ANIMATION_SPEED} + reasonAttributes={{ + context: 'SearchAutocompleteList', + isRecentSearchesDataLoaded, + areOptionsInitialized, + }} /> ), }); @@ -434,7 +445,20 @@ function SearchAutocompleteList({ } return {sections: nextSections, styledRecentReports: nextStyledRecentReports, suggestionsCount: nextSuggestionsCount}; - }, [autocompleteQueryValue, autocompleteSuggestions, expensifyIcons, getAdditionalSections, recentReportsOptions, recentSearchesData, searchOptions, searchQueryItem, styles, translate, areOptionsInitialized]); + }, [ + autocompleteQueryValue, + autocompleteSuggestions, + expensifyIcons, + getAdditionalSections, + recentReportsOptions, + recentSearchesData, + searchOptions, + searchQueryItem, + styles, + translate, + areOptionsInitialized, + isRecentSearchesDataLoaded, + ]); const sectionItemText = sections?.at(1)?.data?.[0]?.text ?? ''; const normalizedReferenceText = sectionItemText.toLowerCase(); @@ -490,12 +514,6 @@ function SearchAutocompleteList({ } }, [autocompleteQueryValue, onHighlightFirstItem, normalizedReferenceText]); - const reasonAttributes: SkeletonSpanReasonAttributes = { - context: 'SearchAutocompleteList', - isRecentSearchesDataLoaded, - areOptionsInitialized, - }; - if (isLoading) { return (