diff --git a/src/components/OptionListContextProvider.tsx b/src/components/OptionListContextProvider.tsx index 5b3db2bf0e8c..f7015fe8caa1 100644 --- a/src/components/OptionListContextProvider.tsx +++ b/src/components/OptionListContextProvider.tsx @@ -1,13 +1,10 @@ -import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; -import {InteractionManager} from 'react-native'; +import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import {useOnyx} from 'react-native-onyx'; import type {OnyxCollection} from 'react-native-onyx'; import usePrevious from '@hooks/usePrevious'; -import getPlatform from '@libs/getPlatform'; import {createOptionFromReport, createOptionList, processReport} from '@libs/OptionsListUtils'; import type {OptionList, SearchOption} from '@libs/OptionsListUtils'; import {isSelfDM} from '@libs/ReportUtils'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, Report} from '@src/types/onyx'; import {usePersonalDetails} from './OnyxProvider'; @@ -45,8 +42,7 @@ const isEqualPersonalDetail = (prevPersonalDetail: PersonalDetails, personalDeta prevPersonalDetail?.displayName === personalDetail?.displayName; function OptionsListContextProvider({children}: OptionsListProviderProps) { - const [areOptionsInitialized, setAreOptionsInitialized] = useState(false); - + const areOptionsInitialized = useRef(false); const [options, setOptions] = useState({ reports: [], personalDetails: [], @@ -71,12 +67,12 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { * This effect is responsible for generating the options list when their data is not yet initialized */ useEffect(() => { - if (!areOptionsInitialized || !reports || hasInitialData) { + if (!areOptionsInitialized.current || !reports || hasInitialData) { return; } loadOptions(); - }, [reports, personalDetails, hasInitialData, loadOptions, areOptionsInitialized]); + }, [reports, personalDetails, hasInitialData, loadOptions]); /** * This effect is responsible for generating the options list when the locale changes @@ -106,7 +102,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { * This effect is responsible for updating the options only for changed reports */ useEffect(() => { - if (!changedReportsEntries || !areOptionsInitialized) { + if (!changedReportsEntries || !areOptionsInitialized.current) { return; } @@ -134,10 +130,10 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { reports: Array.from(updatedReportsMap.values()), }; }); - }, [areOptionsInitialized, changedReportsEntries, personalDetails]); + }, [changedReportsEntries, personalDetails]); useEffect(() => { - if (!changedReportActions || !areOptionsInitialized) { + if (!changedReportActions || !areOptionsInitialized.current) { return; } @@ -166,14 +162,14 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { reports: Array.from(updatedReportsMap.values()), }; }); - }, [areOptionsInitialized, changedReportActions, personalDetails]); + }, [changedReportActions, personalDetails]); /** * This effect is used to update the options list when personal details change. */ useEffect(() => { // there is no need to update the options if the options are not initialized - if (!areOptionsInitialized) { + if (!areOptionsInitialized.current) { return; } @@ -237,30 +233,24 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { const initializeOptions = useCallback(() => { loadOptions(); - if (getPlatform() === CONST.PLATFORM.ANDROID || getPlatform() === CONST.PLATFORM.IOS) { - InteractionManager.runAfterInteractions(() => { - setAreOptionsInitialized(true); - }); - return; - } - setAreOptionsInitialized(true); + areOptionsInitialized.current = true; }, [loadOptions]); const resetOptions = useCallback(() => { - if (!areOptionsInitialized) { + if (!areOptionsInitialized.current) { return; } - setAreOptionsInitialized(false); + areOptionsInitialized.current = false; setOptions({ reports: [], personalDetails: [], }); - }, [areOptionsInitialized]); + }, []); return ( ({options, initializeOptions, areOptionsInitialized, resetOptions}), [options, initializeOptions, areOptionsInitialized, resetOptions])} + value={useMemo(() => ({options, initializeOptions, areOptionsInitialized: areOptionsInitialized.current, resetOptions}), [options, initializeOptions, resetOptions])} > {children} @@ -273,14 +263,15 @@ const useOptionsListContext = () => useContext(OptionsListContext); const useOptionsList = (options?: {shouldInitialize: boolean}) => { const {shouldInitialize = true} = options ?? {}; const {initializeOptions, options: optionsList, areOptionsInitialized, resetOptions} = useOptionsListContext(); + const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: false}); useEffect(() => { - if (!shouldInitialize || areOptionsInitialized) { + if (!shouldInitialize || areOptionsInitialized || isLoadingApp) { return; } initializeOptions(); - }, [shouldInitialize, initializeOptions, areOptionsInitialized]); + }, [shouldInitialize, initializeOptions, areOptionsInitialized, isLoadingApp]); return { initializeOptions, diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 9c00a25b6782..c3ef626869a9 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -5,7 +5,6 @@ import {useOnyx} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import {usePersonalDetails} from '@components/OnyxProvider'; import {useOptionsList} from '@components/OptionListContextProvider'; -import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; import type {AnimatedTextInputRef} from '@components/RNTextInput'; import SelectionList from '@components/SelectionList'; import type {SearchQueryItem, SearchQueryListItemProps} from '@components/SelectionList/Search/SearchQueryListItem'; @@ -370,19 +369,13 @@ function SearchAutocompleteList( .filter((type) => type.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.includes(type.toLowerCase())) .sort(); - return filteredTypes.map((type) => ({ - filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.TYPE, - text: type, - })); + return filteredTypes.map((type) => ({filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.TYPE, text: type})); } case CONST.SEARCH.SYNTAX_ROOT_KEYS.GROUP_BY: { const filteredGroupBy = groupByAutocompleteList.filter( (groupByValue) => groupByValue.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.includes(groupByValue.toLowerCase()), ); - return filteredGroupBy.map((groupByValue) => ({ - filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.GROUP_BY, - text: groupByValue, - })); + return filteredGroupBy.map((groupByValue) => ({filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.GROUP_BY, text: groupByValue})); } case CONST.SEARCH.SYNTAX_ROOT_KEYS.STATUS: { const filteredStatuses = statusAutocompleteList @@ -390,10 +383,7 @@ function SearchAutocompleteList( .sort() .slice(0, 10); - return filteredStatuses.map((status) => ({ - filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.STATUS, - text: status, - })); + return filteredStatuses.map((status) => ({filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.STATUS, text: status})); } case CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPENSE_TYPE: { const filteredExpenseTypes = expenseTypes @@ -559,10 +549,7 @@ function SearchAutocompleteList( text: StringUtils.lineBreaksToSpaces(item.text), wrapperStyle: [styles.pr3, styles.pl3], })); - sections.push({ - title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined, - data: styledRecentReports, - }); + sections.push({title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined, data: styledRecentReports}); if (autocompleteSuggestions.length > 0) { const autocompleteData = autocompleteSuggestions.map(({filterKey, text, autocompleteID, mapKey}) => { @@ -605,14 +592,9 @@ function SearchAutocompleteList( }, [autocompleteQueryValue, onHighlightFirstItem, normalizedReferenceText]); return ( - <> - {isInitialRender && ( - - )} + // On page refresh, when the list is rendered before options are initialized the auto-focusing on initiallyFocusedOptionKey + // will fail because the list will be empty on first render so we only render after options are initialized. + areOptionsInitialized && ( showLoadingPlaceholder={!areOptionsInitialized} fixedNumItemsForLoader={4} @@ -641,7 +623,7 @@ function SearchAutocompleteList( shouldSubscribeToArrowKeyEvents={shouldSubscribeToArrowKeyEvents} disableKeyboardShortcuts={!shouldSubscribeToArrowKeyEvents} /> - + ) ); } diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 246a32435130..543c7e3f8045 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -7,8 +7,6 @@ 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 OptionsListSkeletonView from '@components/OptionsListSkeletonView'; import type {AnimatedTextInputRef} from '@components/RNTextInput'; import type {GetAdditionalSectionsCallback} from '@components/Search/SearchAutocompleteList'; import SearchAutocompleteList from '@components/Search/SearchAutocompleteList'; @@ -81,10 +79,8 @@ type SearchRouterProps = { function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDisplayed}: SearchRouterProps, ref: React.Ref) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const [, recentSearchesMetadata] = useOnyx(ONYXKEYS.RECENT_SEARCHES, {canBeMissing: true}); - const isRecentSearchesDataLoaded = !isLoadingOnyxValue(recentSearchesMetadata); - const {areOptionsInitialized} = useOptionsList(); + const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const {shouldUseNarrowLayout} = useResponsiveLayout(); const listRef = useRef(null); @@ -320,7 +316,8 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla }); const modalWidth = shouldUseNarrowLayout ? styles.w100 : {width: variables.searchRouterPopoverWidth}; - const shouldShowSearchList = areOptionsInitialized && isRecentSearchesDataLoaded; + const isRecentSearchesDataLoaded = !isLoadingOnyxValue(recentSearchesMetadata); + return ( )} - <> - { - const focusedOption = listRef.current?.getFocusedOption(); - - if (!focusedOption) { - submitSearch(textInputValue); - return; - } - - onListItemPress(focusedOption); - }} - caretHidden={shouldHideInputCaret} - autocompleteListRef={listRef} - shouldShowOfflineMessage - wrapperStyle={{...styles.border, ...styles.alignItemsCenter}} - outerWrapperStyle={[shouldUseNarrowLayout ? styles.mv3 : styles.mv2, shouldUseNarrowLayout ? styles.mh5 : styles.mh2]} - wrapperFocusedStyle={styles.borderColorFocus} - isSearchingForReports={isSearchingForReports} - selection={selection} - substitutionMap={autocompleteSubstitutions} - ref={textInputRef} - /> - {shouldShowSearchList && ( + {isRecentSearchesDataLoaded && ( + <> + { + const focusedOption = listRef.current?.getFocusedOption(); + + if (!focusedOption) { + submitSearch(textInputValue); + return; + } + + onListItemPress(focusedOption); + }} + caretHidden={shouldHideInputCaret} + autocompleteListRef={listRef} + shouldShowOfflineMessage + wrapperStyle={{...styles.border, ...styles.alignItemsCenter}} + outerWrapperStyle={[shouldUseNarrowLayout ? styles.mv3 : styles.mv2, shouldUseNarrowLayout ? styles.mh5 : styles.mh2]} + wrapperFocusedStyle={styles.borderColorFocus} + isSearchingForReports={isSearchingForReports} + selection={selection} + substitutionMap={autocompleteSubstitutions} + ref={textInputRef} + /> - )} - {!shouldShowSearchList && ( - - )} - + + )} ); }