-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Refactor search functionality to improve loading state handling #83917
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
5af0782
a419593
f49723d
33f9840
87d6f70
803f2e6
7075730
c389d60
e0c5f7d
8e8480d
8067359
833048d
da450a5
92140a6
89dc6da
6fe3bff
c4ad5e2
db145a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import React from 'react'; | ||
| import type {StyleProp, ViewStyle} from 'react-native'; | ||
| import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; | ||
| import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import {endSpanWithAttributes} from '@libs/telemetry/activeSpans'; | ||
| import CONST from '@src/CONST'; | ||
|
|
||
| type SearchLoadingSkeletonProps = { | ||
| containerStyle?: StyleProp<ViewStyle>; | ||
| }; | ||
|
|
||
| function SearchLoadingSkeleton({containerStyle}: SearchLoadingSkeletonProps) { | ||
| const styles = useThemeStyles(); | ||
|
|
||
| return ( | ||
| <Animated.View | ||
| entering={FadeIn.duration(CONST.SEARCH.ANIMATION.FADE_DURATION)} | ||
| exiting={FadeOut.duration(CONST.SEARCH.ANIMATION.FADE_DURATION)} | ||
| style={[styles.flex1]} | ||
| onLayout={() => { | ||
| endSpanWithAttributes(CONST.TELEMETRY.SPAN_NAVIGATE_TO_REPORTS, {[CONST.TELEMETRY.ATTRIBUTE_IS_WARM]: false}); | ||
| }} | ||
| > | ||
| <SearchRowSkeleton | ||
| shouldAnimate | ||
| containerStyle={containerStyle} | ||
| /> | ||
| </Animated.View> | ||
| ); | ||
| } | ||
|
|
||
| export default SearchLoadingSkeleton; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; | |
| import type {NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; | ||
| import {View} from 'react-native'; | ||
| import type {OnyxEntry} from 'react-native-onyx'; | ||
| import Animated, {FadeIn, FadeOut, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; | ||
| import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; | ||
| import FullPageErrorView from '@components/BlockingViews/FullPageErrorView'; | ||
| import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; | ||
| import {ModalActions} from '@components/Modal/Global/ModalContext'; | ||
|
|
@@ -37,7 +37,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; | |
| import {openOldDotLink} from '@libs/actions/Link'; | ||
| import {turnOffMobileSelectionMode, turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; | ||
| import type {TransactionPreviewData} from '@libs/actions/Search'; | ||
| import {openSearch, setOptimisticDataForTransactionThreadPreview} from '@libs/actions/Search'; | ||
| import {setOptimisticDataForTransactionThreadPreview} from '@libs/actions/Search'; | ||
| import {canUseTouchScreen} from '@libs/DeviceCapabilities'; | ||
| import Log from '@libs/Log'; | ||
| import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; | ||
|
|
@@ -52,6 +52,7 @@ import { | |
| getSections, | ||
| getSortedSections, | ||
| getSuggestedSearches, | ||
| getValidGroupBy, | ||
| getWideAmountIndicators, | ||
| isGroupedItemArray, | ||
| isReportActionListItemType, | ||
|
|
@@ -230,16 +231,8 @@ function Search({ | |
| const {markReportIDAsExpense} = useWideRHPActions(); | ||
| const {currentSearchHash, selectedTransactions, shouldTurnOffSelectionMode, lastSearchType, areAllMatchingItemsSelected, shouldResetSearchQuery, shouldUseLiveData} = | ||
| useSearchStateContext(); | ||
| const { | ||
| setCurrentSearchHashAndKey, | ||
| setCurrentSearchQueryJSON, | ||
| setSelectedTransactions, | ||
| clearSelectedTransactions, | ||
| setShouldShowFiltersBarLoading, | ||
| setShouldShowSelectAllMatchingItems, | ||
| selectAllMatchingItems, | ||
| setShouldResetSearchQuery, | ||
| } = useSearchActionsContext(); | ||
| const {setSelectedTransactions, clearSelectedTransactions, setShouldShowFiltersBarLoading, setShouldShowSelectAllMatchingItems, selectAllMatchingItems, setShouldResetSearchQuery} = | ||
| useSearchActionsContext(); | ||
| const [offset, setOffset] = useState(0); | ||
|
|
||
| const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); | ||
|
|
@@ -305,22 +298,7 @@ function Search({ | |
| } | ||
| }, [onDEWModalOpen, showConfirmModal, translate]); | ||
|
|
||
| const clearTransactionsAndSetHashAndKey = useCallback(() => { | ||
| clearSelectedTransactions(hash); | ||
| setCurrentSearchHashAndKey(hash, recentSearchHash, searchKey); | ||
| setCurrentSearchQueryJSON(queryJSON); | ||
| }, [hash, recentSearchHash, searchKey, clearSelectedTransactions, setCurrentSearchHashAndKey, setCurrentSearchQueryJSON, queryJSON]); | ||
|
|
||
| useFocusEffect(clearTransactionsAndSetHashAndKey); | ||
|
|
||
| useEffect(() => { | ||
| clearTransactionsAndSetHashAndKey(); | ||
|
|
||
| // Trigger once on mount (e.g., on page reload), when RHP is open and screen is not focused | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, []); | ||
|
|
||
| const validGroupBy = groupBy && Object.values(CONST.SEARCH.GROUP_BY).includes(groupBy) ? groupBy : undefined; | ||
| const validGroupBy = getValidGroupBy(groupBy); | ||
| const prevValidGroupBy = usePrevious(validGroupBy); | ||
| const isSearchResultsEmpty = !searchResults?.data || isSearchResultsEmptyUtil(searchResults, validGroupBy); | ||
|
|
||
|
|
@@ -368,17 +346,6 @@ function Search({ | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [isSmallScreenWidth]); | ||
|
|
||
| useEffect(() => { | ||
| openSearch({includePartiallySetupBankAccounts: true}); | ||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| if (!prevIsOffline || isOffline) { | ||
| return; | ||
| } | ||
| openSearch({includePartiallySetupBankAccounts: true}); | ||
| }, [isOffline, prevIsOffline]); | ||
|
|
||
| const {newSearchResultKeys, handleSelectionListScroll, newTransactions} = useSearchHighlightAndScroll({ | ||
| searchResults, | ||
| transactions, | ||
|
|
@@ -408,21 +375,6 @@ function Search({ | |
| isCardFeedsLoading); | ||
| const shouldShowLoadingMoreItems = !shouldShowLoadingState && searchResults?.search?.isLoading && searchResults?.search?.offset > 0; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we take
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, no - these serve different purposes. useSearchLoadingState gates whether the Search component should mount at all (page-level skeleton vs component). The internal shouldShowLoadingState in Search/index.tsx controls post-mount behavior (loading bar in TopBar, "load more" skeleton, telemetry warm/cold attribute, etc.) and has additional conditions like hasErrors && searchRequestResponseStatusCode === null that are only relevant once Search is mounted. Merging them would conflate two different concerns. |
||
|
|
||
| const loadingSkeletonReasonAttributes = useMemo<SkeletonSpanReasonAttributes>( | ||
| () => ({ | ||
| context: 'Search', | ||
| isOffline, | ||
| isDataLoaded, | ||
| isCardFeedsLoading, | ||
| isSearchLoading: !!searchResults?.search?.isLoading, | ||
| hasEmptyData: Array.isArray(searchResults?.data) && searchResults?.data.length === 0, | ||
| hasErrors, | ||
| hasPendingResponse: searchRequestResponseStatusCode === null, | ||
| shouldUseLiveData, | ||
| }), | ||
| [isOffline, isDataLoaded, isCardFeedsLoading, searchResults?.search?.isLoading, searchResults?.data, hasErrors, searchRequestResponseStatusCode, shouldUseLiveData], | ||
| ); | ||
|
|
||
| const loadMoreSkeletonReasonAttributes = useMemo<SkeletonSpanReasonAttributes>( | ||
| () => ({ | ||
| context: 'Search.ListFooter', | ||
|
|
@@ -1249,11 +1201,6 @@ function Search({ | |
| spanExistedOnMount.current = false; | ||
| }, []); | ||
|
|
||
| const onLayoutSkeleton = useCallback(() => { | ||
| hasHadFirstLayout.current = true; | ||
| endSpanWithAttributes(CONST.TELEMETRY.SPAN_NAVIGATE_TO_REPORTS, {[CONST.TELEMETRY.ATTRIBUTE_IS_WARM]: false}); | ||
| }, []); | ||
|
|
||
| const onLayoutChart = useCallback(() => { | ||
| hasHadFirstLayout.current = true; | ||
| endSpanWithAttributes(CONST.TELEMETRY.SPAN_NAVIGATE_TO_REPORTS, {[CONST.TELEMETRY.ATTRIBUTE_IS_WARM]: true}); | ||
|
|
@@ -1274,23 +1221,6 @@ function Search({ | |
| }, [shouldShowLoadingState]), | ||
| ); | ||
|
|
||
| if (shouldShowLoadingState) { | ||
| return ( | ||
| <Animated.View | ||
| entering={FadeIn.duration(CONST.SEARCH.ANIMATION.FADE_DURATION)} | ||
| exiting={FadeOut.duration(CONST.SEARCH.ANIMATION.FADE_DURATION)} | ||
| style={[styles.flex1]} | ||
| onLayout={onLayoutSkeleton} | ||
| > | ||
| <SearchRowSkeleton | ||
| shouldAnimate | ||
| containerStyle={shouldUseNarrowLayout ? styles.searchListContentContainerStyles : styles.mt3} | ||
| reasonAttributes={loadingSkeletonReasonAttributes} | ||
| /> | ||
| </Animated.View> | ||
| ); | ||
| } | ||
|
|
||
| if (searchResults === undefined) { | ||
| Log.alert('[Search] Undefined search type'); | ||
| cancelNavigationSpans(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||||||||||||||||||
| import {useSearchStateContext} from '@components/Search/SearchContext'; | ||||||||||||||||||||||||||||||
| import type {SearchQueryJSON} from '@components/Search/types'; | ||||||||||||||||||||||||||||||
| import {getValidGroupBy, isSearchDataLoaded} from '@libs/SearchUIUtils'; | ||||||||||||||||||||||||||||||
| import CONST from '@src/CONST'; | ||||||||||||||||||||||||||||||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||||||||||||||||||||||||||||||
| import useNetwork from './useNetwork'; | ||||||||||||||||||||||||||||||
| import useOnyx from './useOnyx'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||
| * Computes whether the search page should show a loading skeleton | ||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||
| function useSearchLoadingState(queryJSON: SearchQueryJSON | undefined): boolean { | ||||||||||||||||||||||||||||||
| const {isOffline} = useNetwork(); | ||||||||||||||||||||||||||||||
| const {shouldUseLiveData, currentSearchResults} = useSearchStateContext(); | ||||||||||||||||||||||||||||||
| const [, cardFeedsResult] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (shouldUseLiveData || isOffline || !queryJSON) { | ||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const isDataLoaded = isSearchDataLoaded(currentSearchResults, queryJSON); | ||||||||||||||||||||||||||||||
| const isLoadingWithNoData = !!currentSearchResults?.search?.isLoading && Array.isArray(currentSearchResults?.data) && currentSearchResults.data.length === 0; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const validGroupBy = getValidGroupBy(queryJSON.groupBy); | ||||||||||||||||||||||||||||||
| const isCardFeedsLoading = validGroupBy === CONST.SEARCH.GROUP_BY.CARD && cardFeedsResult?.status === 'loading'; | ||||||||||||||||||||||||||||||
szymonzalarski98 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const hasNoData = currentSearchResults?.data === undefined; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return (!isDataLoaded && hasNoData) || isLoadingWithNoData || isCardFeedsLoading; | ||||||||||||||||||||||||||||||
szymonzalarski98 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| // There's a race condition in Onyx which makes it return data from the previous Search, so in addition to checking that the data is loaded | |
| // we also need to check that the searchResults matches the type and status of the current search | |
| const isDataLoaded = shouldUseLiveData || isSearchDataLoaded(searchResults, queryJSON); | |
| const hasErrors = Object.keys(searchResults?.errors ?? {}).length > 0 && !isOffline; | |
| // For to-do searches, we never show loading state since the data is always available locally from Onyx | |
| const shouldShowLoadingState = | |
| !shouldUseLiveData && | |
| !isOffline && | |
| (!isDataLoaded || | |
| (!!searchResults?.search.isLoading && Array.isArray(searchResults?.data) && searchResults?.data.length === 0) || | |
| (hasErrors && searchRequestResponseStatusCode === null) || | |
| isCardFeedsLoading); |
the way we calculated isDataLoaded didn't only check for existence to avoid race condition as depicted in the comment and also there were two cases you omitted here
(!!searchResults?.search.isLoading && Array.isArray(searchResults?.data) && searchResults?.data.length === 0) ||
and
isCardFeedsLoading
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import {useCallback, useEffect} from 'react'; | ||
| import {useFocusEffect} from '@react-navigation/native'; | ||
| import {useSearchActionsContext, useSearchStateContext} from '@components/Search/SearchContext'; | ||
| import type {SearchQueryJSON} from '@components/Search/types'; | ||
| import {openSearch, search} from '@libs/actions/Search'; | ||
| import {getSuggestedSearches, isSearchDataLoaded} from '@libs/SearchUIUtils'; | ||
| import useCardFeedsForDisplay from './useCardFeedsForDisplay'; | ||
| import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails'; | ||
| import useNetwork from './useNetwork'; | ||
| import usePrevious from './usePrevious'; | ||
|
|
||
| /** | ||
| * Handles page-level setup for Search that must happen before the Search component mounts: | ||
| * - Sets the context hash/key so the Onyx subscription points to the correct snapshot | ||
| * - Fires the search() API call so data starts loading alongside the skeleton | ||
| * - Fires openSearch() to load bank account data | ||
| * - Re-fires openSearch() when coming back online | ||
| */ | ||
| function useSearchPageSetup(queryJSON: SearchQueryJSON | undefined) { | ||
| const {isOffline} = useNetwork(); | ||
| const prevIsOffline = usePrevious(isOffline); | ||
| const {setCurrentSearchHashAndKey, setCurrentSearchQueryJSON, clearSelectedTransactions} = useSearchActionsContext(); | ||
| const {shouldUseLiveData, currentSearchResults} = useSearchStateContext(); | ||
| const {accountID} = useCurrentUserPersonalDetails(); | ||
| const {defaultCardFeed} = useCardFeedsForDisplay(); | ||
|
|
||
| const suggestedSearches = getSuggestedSearches(accountID, defaultCardFeed?.id); | ||
| const hash = queryJSON?.hash; | ||
| const recentSearchHash = queryJSON?.recentSearchHash; | ||
| const searchKey = recentSearchHash !== undefined ? Object.values(suggestedSearches).find((s) => s.recentSearchHash === recentSearchHash)?.key : undefined; | ||
|
|
||
| // useCallback is required here because useFocusEffect (React Navigation external API) compares callback references. | ||
| // React Compiler cannot optimize this — it doesn't know useFocusEffect's internal semantics. | ||
| const syncContextWithRoute = useCallback(() => { | ||
| if (hash === undefined || recentSearchHash === undefined || !queryJSON) { | ||
| return; | ||
| } | ||
| clearSelectedTransactions(hash); | ||
| setCurrentSearchHashAndKey(hash, recentSearchHash, searchKey); | ||
| setCurrentSearchQueryJSON(queryJSON); | ||
| }, [hash, recentSearchHash, searchKey, queryJSON, clearSelectedTransactions, setCurrentSearchHashAndKey, setCurrentSearchQueryJSON]); | ||
|
|
||
| useFocusEffect(syncContextWithRoute); | ||
|
|
||
| useEffect(syncContextWithRoute, [syncContextWithRoute]); | ||
szymonzalarski98 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| useEffect(() => { | ||
| if (!queryJSON || hash === undefined || shouldUseLiveData || isOffline) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ PERF-12 (docs)
Wrap const syncContextWithRoute = useCallback(() => {
if (hash === undefined || recentSearchHash === undefined || !queryJSON) {
return;
}
clearSelectedTransactions(hash);
setCurrentSearchHashAndKey(hash, recentSearchHash, searchKey);
setCurrentSearchQueryJSON(queryJSON);
}, [hash, recentSearchHash, searchKey, clearSelectedTransactions, setCurrentSearchHashAndKey, setCurrentSearchQueryJSON, queryJSON]);Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency. |
||
| return; | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ PERF-9 (docs)
This is directly related to the missing Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency. |
||
| if (isSearchDataLoaded(currentSearchResults, queryJSON) || currentSearchResults?.search?.isLoading) { | ||
| return; | ||
| } | ||
| search({queryJSON, searchKey, offset: 0, shouldCalculateTotals: false, isLoading: false}); | ||
|
||
| }, [hash, searchKey, isOffline, shouldUseLiveData, currentSearchResults, queryJSON]); | ||
|
|
||
| useEffect(() => { | ||
| openSearch({includePartiallySetupBankAccounts: true}); | ||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| if (!prevIsOffline || isOffline) { | ||
| return; | ||
| } | ||
| openSearch({includePartiallySetupBankAccounts: true}); | ||
| }, [isOffline, prevIsOffline]); | ||
| } | ||
|
|
||
| export default useSearchPageSetup; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; | |
| import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider'; | ||
| import Search from '@components/Search'; | ||
| import {useSearchActionsContext} from '@components/Search/SearchContext'; | ||
| import SearchLoadingSkeleton from '@components/Search/SearchLoadingSkeleton'; | ||
| import SearchPageFooter from '@components/Search/SearchPageFooter'; | ||
| import SearchFiltersBar from '@components/Search/SearchPageHeader/SearchFiltersBar'; | ||
| import SearchPageHeader from '@components/Search/SearchPageHeader/SearchPageHeader'; | ||
|
|
@@ -21,6 +22,7 @@ import useAndroidBackButtonHandler from '@hooks/useAndroidBackButtonHandler'; | |
| import useLoadingBarVisibility from '@hooks/useLoadingBarVisibility'; | ||
| import useLocalize from '@hooks/useLocalize'; | ||
| import useNetwork from '@hooks/useNetwork'; | ||
| import useSearchLoadingState from '@hooks/useSearchLoadingState'; | ||
| import useResponsiveLayout from '@hooks/useResponsiveLayout'; | ||
| import useScrollEventEmitter from '@hooks/useScrollEventEmitter'; | ||
| import useStyleUtils from '@hooks/useStyleUtils'; | ||
|
|
@@ -55,6 +57,7 @@ type SearchPageNarrowProps = { | |
| }; | ||
|
|
||
| function SearchPageNarrow({queryJSON, searchResults, isMobileSelectionModeEnabled, metadata, footerData, shouldShowFooter}: SearchPageNarrowProps) { | ||
| const shouldShowLoadingSkeleton = useSearchLoadingState(queryJSON); | ||
|
||
| const {translate} = useLocalize(); | ||
| const {shouldUseNarrowLayout} = useResponsiveLayout(); | ||
| const {windowHeight} = useWindowDimensions(); | ||
|
|
@@ -238,16 +241,20 @@ function SearchPageNarrow({queryJSON, searchResults, isMobileSelectionModeEnable | |
| )} | ||
| {!searchRouterListVisible && ( | ||
| <View style={[styles.flex1]}> | ||
| <Search | ||
| searchResults={searchResults} | ||
| key={queryJSON.hash} | ||
| queryJSON={queryJSON} | ||
| onSearchListScroll={scrollHandler} | ||
| contentContainerStyle={!isMobileSelectionModeEnabled ? styles.searchListContentContainerStyles : undefined} | ||
| handleSearch={handleSearchAction} | ||
| isMobileSelectionModeEnabled={isMobileSelectionModeEnabled} | ||
| searchRequestResponseStatusCode={searchRequestResponseStatusCode} | ||
| /> | ||
| {shouldShowLoadingSkeleton ? ( | ||
| <SearchLoadingSkeleton containerStyle={styles.searchListContentContainerStyles} /> | ||
szymonzalarski98 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ) : ( | ||
| <Search | ||
| searchResults={searchResults} | ||
| key={queryJSON.hash} | ||
| queryJSON={queryJSON} | ||
| onSearchListScroll={scrollHandler} | ||
| contentContainerStyle={!isMobileSelectionModeEnabled ? styles.searchListContentContainerStyles : undefined} | ||
| handleSearch={handleSearchAction} | ||
| isMobileSelectionModeEnabled={isMobileSelectionModeEnabled} | ||
| searchRequestResponseStatusCode={searchRequestResponseStatusCode} | ||
| /> | ||
| )} | ||
| </View> | ||
| )} | ||
| {shouldShowFooter && !searchRouterListVisible && ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also remove the skeleton logic from SearchList since we now do this up the tree
App/src/components/Search/index.tsx
Lines 1210 to 1224 in eed56d0