diff --git a/src/components/Search/SearchList/index.tsx b/src/components/Search/SearchList/index.tsx index 264ea889854..546ce3cc27f 100644 --- a/src/components/Search/SearchList/index.tsx +++ b/src/components/Search/SearchList/index.tsx @@ -39,7 +39,6 @@ import useSafeAreaPaddings from '@hooks/useSafeAreaPaddings'; import useThemeStyles from '@hooks/useThemeStyles'; import {turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import navigationRef from '@libs/Navigation/navigationRef'; -import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Transaction, TransactionViolations} from '@src/types/onyx'; @@ -268,7 +267,7 @@ function SearchList({ return; } - listRef.current.scrollToIndex({index, animated, viewOffset: variables.contentHeaderHeight}); + listRef.current.scrollToIndex({index, animated, viewPosition: 0}); }, [data], ); diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index c949d013a67..cb4df1313f0 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -1,6 +1,6 @@ import {findFocusedRoute, useFocusEffect, useIsFocused, useNavigation} from '@react-navigation/native'; import {accountIDSelector} from '@selectors/Session'; -import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; import type {NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import Animated, {FadeIn, FadeOut, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; @@ -288,7 +288,6 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS [reportActions], ); const {translate, localeCompare, formatPhoneNumber} = useLocalize(); - const searchListRef = useRef(null); const clearTransactionsAndSetHashAndKey = useCallback(() => { clearSelectedTransactions(hash); @@ -732,6 +731,16 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS [type, status, data, sortBy, sortOrder, groupBy, isChat, newSearchResultKey, selectedTransactions, canSelectMultiple, localeCompare, hash], ); + const searchListRef = useRef(null); + + useLayoutEffect(() => { + handleSelectionListScroll(sortedSelectedData, searchListRef.current); + + // We only need to run the effect on change of newSearchResultKey to scroll to the new item. + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [newSearchResultKey]); + const hasErrors = Object.keys(searchResults?.errors ?? {}).length > 0 && !isOffline; useEffect(() => { @@ -790,8 +799,6 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS ); }, [clearSelectedTransactions, data, groupBy, reportActionsArray, selectedTransactions, setSelectedTransactions, outstandingReportsByPolicyID]); - const onLayout = useCallback(() => handleSelectionListScroll(sortedSelectedData, searchListRef.current), [handleSelectionListScroll, sortedSelectedData]); - const areAllOptionalColumnsHidden = useMemo(() => { const canBeMissingColumns = expenseHeaders.filter((header) => header.canBeMissing).map((header) => header.columnName); return canBeMissingColumns.every((column) => !columnsToShow.includes(column)); @@ -905,7 +912,6 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS columns={columnsToShow} areAllOptionalColumnsHidden={areAllOptionalColumnsHidden} violations={violations} - onLayout={onLayout} isMobileSelectionModeEnabled={isMobileSelectionModeEnabled} shouldAnimate={type === CONST.SEARCH.DATA_TYPES.EXPENSE} newTransactions={newTransactions} diff --git a/src/hooks/useSearchHighlightAndScroll.ts b/src/hooks/useSearchHighlightAndScroll.ts index a7551339e23..20aa2b98e6b 100644 --- a/src/hooks/useSearchHighlightAndScroll.ts +++ b/src/hooks/useSearchHighlightAndScroll.ts @@ -44,7 +44,7 @@ function useSearchHighlightAndScroll({ // Ref to track if the search was triggered by this hook const triggeredByHookRef = useRef(false); const searchTriggeredRef = useRef(false); - const hasNewItemsRef = useRef(false); + const newTransactionIDsRef = useRef([]); const previousSearchResults = usePrevious(searchResults?.data); const [newSearchResultKey, setNewSearchResultKey] = useState(null); const highlightedIDs = useRef>(new Set()); @@ -126,7 +126,9 @@ function useSearchHighlightAndScroll({ // We only want to highlight new items if the addition of transactions or report actions triggered the search. // This is because, on deletion of items, the backend sometimes returns old items in place of the deleted ones. // We don't want to highlight these old items, even if they appear new in the current search results. - hasNewItemsRef.current = isChat ? reportActionsIDs.length > previousReportActionsIDs.length : transactionsIDs.length > previousTransactionsIDs.length; + if (!isChat && hasTransactionsIDsChange) { + newTransactionIDsRef.current.push(...transactionsIDs.filter((id) => !previousTransactionsIDs.includes(id)).map((id) => id.replace(ONYXKEYS.COLLECTION.TRANSACTION, ''))); + } // Set the flag indicating the search is triggered by the hook triggeredByHookRef.current = true; @@ -178,7 +180,7 @@ function useSearchHighlightAndScroll({ // Find new report action IDs that are not in the previousReportActionIDs and not already highlighted const newReportActionIDs = currentReportActionIDs.filter((id) => !previousReportActionIDs.includes(id) && !highlightedIDs.current.has(id)); - if (!triggeredByHookRef.current || newReportActionIDs.length === 0 || !hasNewItemsRef.current) { + if (!triggeredByHookRef.current || newReportActionIDs.length === 0) { return; } @@ -192,12 +194,16 @@ function useSearchHighlightAndScroll({ const currentTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data); // Find new transaction IDs that are not in the previousTransactionIDs and not already highlighted - const newTransactionIDs = currentTransactionIDs.filter((id) => !previousTransactionIDs.includes(id) && !highlightedIDs.current.has(id)); + const newTransactionIDs = currentTransactionIDs.filter( + (id) => !previousTransactionIDs.includes(id) && !highlightedIDs.current.has(id) && newTransactionIDsRef.current.includes(id), + ); - if (!triggeredByHookRef.current || newTransactionIDs.length === 0 || !hasNewItemsRef.current) { + if (!triggeredByHookRef.current || newTransactionIDs.length === 0) { return; } + // As the new transactions are going to be highlighted below, which would make them obsolete, we will remove them from newTransactionIDsRef. + newTransactionIDsRef.current = newTransactionIDsRef.current.filter((id) => !newTransactionIDs.includes(id)); const newTransactionID = newTransactionIDs.at(0) ?? ''; const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${newTransactionID}`; @@ -255,7 +261,7 @@ function useSearchHighlightAndScroll({ }); // Early return if the new item is not found in the data array - if (indexOfNewItem <= 0) { + if (indexOfNewItem < 0) { return; }