1+ import isEqual from 'lodash/isEqual' ;
12import { useCallback , useEffect , useRef , useState } from 'react' ;
2- import type { OnyxEntry } from 'react-native-onyx' ;
3+ import type { OnyxCollection , OnyxEntry } from 'react-native-onyx' ;
34import type { SearchQueryJSON } from '@components/Search/types' ;
4- import type { ReportListItemType , SearchListItem , SelectionListHandle , TransactionListItemType } from '@components/SelectionList/types' ;
5+ import type { ReportActionListItemType , ReportListItemType , SelectionListHandle , TransactionListItemType } from '@components/SelectionList/types' ;
6+ import { search } from '@libs/actions/Search' ;
57import { isReportActionEntry } from '@libs/SearchUIUtils' ;
68import CONST from '@src/CONST' ;
79import ONYXKEYS from '@src/ONYXKEYS' ;
8- import type { SearchResults } from '@src/types/onyx' ;
10+ import type { ReportActions , SearchResults , Transaction } from '@src/types/onyx' ;
911import usePrevious from './usePrevious' ;
1012
1113type UseSearchHighlightAndScroll = {
1214 searchResults : OnyxEntry < SearchResults > ;
15+ transactions : OnyxCollection < Transaction > ;
16+ previousTransactions : OnyxCollection < Transaction > ;
17+ reportActions : OnyxCollection < ReportActions > ;
18+ previousReportActions : OnyxCollection < ReportActions > ;
1319 queryJSON : SearchQueryJSON ;
20+ offset : number ;
1421} ;
1522
1623/**
17- * Hook used to handle highlighting and scrolling for new search results .
24+ * Hook used to trigger a search when a new transaction or report action is added and handle highlighting and scrolling .
1825 */
19- function useSearchHighlightAndScroll ( { searchResults, queryJSON} : UseSearchHighlightAndScroll ) {
26+ function useSearchHighlightAndScroll ( { searchResults, transactions, previousTransactions, reportActions, previousReportActions, queryJSON, offset} : UseSearchHighlightAndScroll ) {
27+ // Ref to track if the search was triggered by this hook
28+ const triggeredByHookRef = useRef ( false ) ;
29+ const searchTriggeredRef = useRef ( false ) ;
30+ const hasNewItemsRef = useRef ( false ) ;
31+ const previousSearchResults = usePrevious ( searchResults ?. data ) ;
2032 const [ newSearchResultKey , setNewSearchResultKey ] = useState < string | null > ( null ) ;
2133 const highlightedIDs = useRef < Set < string > > ( new Set ( ) ) ;
2234 const initializedRef = useRef ( false ) ;
23- const previousSearchResults = usePrevious ( searchResults ?. data ) ;
2435 const isChat = queryJSON . type === CONST . SEARCH . DATA_TYPES . CHAT ;
2536
37+ // Trigger search when a new report action is added while on chat or when a new transaction is added for the other search types.
38+ useEffect ( ( ) => {
39+ const previousTransactionsIDs = Object . keys ( previousTransactions ?? { } ) ;
40+ const transactionsIDs = Object . keys ( transactions ?? { } ) ;
41+
42+ const reportActionsIDs = Object . values ( reportActions ?? { } )
43+ . map ( ( actions ) => Object . keys ( actions ?? { } ) )
44+ . flat ( ) ;
45+ const previousReportActionsIDs = Object . values ( previousReportActions ?? { } )
46+ . map ( ( actions ) => Object . keys ( actions ?? { } ) )
47+ . flat ( ) ;
48+
49+ if ( searchTriggeredRef . current ) {
50+ return ;
51+ }
52+ const hasTransactionsIDsChange = ! isEqual ( transactionsIDs , previousTransactionsIDs ) ;
53+ const hasReportActionsIDsChange = ! isEqual ( reportActionsIDs , previousReportActionsIDs ) ;
54+
55+ // Check if there is a change in the transactions or report actions list
56+ if ( ( ! isChat && hasTransactionsIDsChange ) || hasReportActionsIDsChange ) {
57+ // We only want to highlight new items if the addition of transactions or report actions triggered the search.
58+ // This is because, on deletion of items, the backend sometimes returns old items in place of the deleted ones.
59+ // We don't want to highlight these old items, even if they appear new in the current search results.
60+ hasNewItemsRef . current = isChat ? reportActionsIDs . length > previousReportActionsIDs . length : transactionsIDs . length > previousTransactionsIDs . length ;
61+
62+ // Set the flag indicating the search is triggered by the hook
63+ triggeredByHookRef . current = true ;
64+
65+ // Trigger the search
66+ search ( { queryJSON, offset} ) ;
67+
68+ // Set the ref to prevent further triggers until reset
69+ searchTriggeredRef . current = true ;
70+ }
71+
72+ // Reset the ref when transactions or report actions in chat search type are updated
73+ return ( ) => {
74+ searchTriggeredRef . current = false ;
75+ } ;
76+ } , [ transactions , previousTransactions , queryJSON , offset , reportActions , previousReportActions , isChat ] ) ;
77+
2678 // Initialize the set with existing IDs only once
2779 useEffect ( ( ) => {
2880 if ( initializedRef . current || ! searchResults ?. data ) {
@@ -34,7 +86,7 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli
3486 initializedRef . current = true ;
3587 } , [ searchResults ?. data , isChat ] ) ;
3688
37- // Detect new items in search results after a change in transactions or report actions
89+ // Detect new items ( transactions or report actions)
3890 useEffect ( ( ) => {
3991 if ( ! previousSearchResults || ! searchResults ?. data ) {
4092 return ;
@@ -46,7 +98,7 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli
4698 // Find new report action IDs that are not in the previousReportActionIDs and not already highlighted
4799 const newReportActionIDs = currentReportActionIDs . filter ( ( id ) => ! previousReportActionIDs . includes ( id ) && ! highlightedIDs . current . has ( id ) ) ;
48100
49- if ( newReportActionIDs . length === 0 ) {
101+ if ( ! triggeredByHookRef . current || newReportActionIDs . length === 0 || ! hasNewItemsRef . current ) {
50102 return ;
51103 }
52104
@@ -55,25 +107,23 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli
55107
56108 setNewSearchResultKey ( newReportActionKey ) ;
57109 highlightedIDs . current . add ( newReportActionID ) ;
58- return ;
59- }
60-
61- // For expenses/transactions
62- const previousTransactionIDs = extractTransactionIDsFromSearchResults ( previousSearchResults ) ;
63- const currentTransactionIDs = extractTransactionIDsFromSearchResults ( searchResults . data ) ;
110+ } else {
111+ const previousTransactionIDs = extractTransactionIDsFromSearchResults ( previousSearchResults ) ;
112+ const currentTransactionIDs = extractTransactionIDsFromSearchResults ( searchResults . data ) ;
64113
65- // Find new transaction IDs not in previous search results and not already highlighted
66- const newTransactionIDs = currentTransactionIDs . filter ( ( id ) => ! previousTransactionIDs . includes ( id ) && ! highlightedIDs . current . has ( id ) ) ;
114+ // Find new transaction IDs that are not in the previousTransactionIDs and not already highlighted
115+ const newTransactionIDs = currentTransactionIDs . filter ( ( id ) => ! previousTransactionIDs . includes ( id ) && ! highlightedIDs . current . has ( id ) ) ;
67116
68- if ( newTransactionIDs . length === 0 ) {
69- return ;
70- }
117+ if ( ! triggeredByHookRef . current || newTransactionIDs . length === 0 || ! hasNewItemsRef . current ) {
118+ return ;
119+ }
71120
72- const newTransactionID = newTransactionIDs . at ( 0 ) ?? '' ;
73- const newTransactionKey = `${ ONYXKEYS . COLLECTION . TRANSACTION } ${ newTransactionID } ` ;
121+ const newTransactionID = newTransactionIDs . at ( 0 ) ?? '' ;
122+ const newTransactionKey = `${ ONYXKEYS . COLLECTION . TRANSACTION } ${ newTransactionID } ` ;
74123
75- setNewSearchResultKey ( newTransactionKey ) ;
76- highlightedIDs . current . add ( newTransactionID ) ;
124+ setNewSearchResultKey ( newTransactionKey ) ;
125+ highlightedIDs . current . add ( newTransactionID ) ;
126+ }
77127 } , [ searchResults ?. data , previousSearchResults , isChat ] ) ;
78128
79129 // Reset newSearchResultKey after it's been used
@@ -93,10 +143,10 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli
93143 * Callback to handle scrolling to the new search result.
94144 */
95145 const handleSelectionListScroll = useCallback (
96- ( data : SearchListItem [ ] ) => ( ref : SelectionListHandle | null ) => {
146+ ( data : Array < TransactionListItemType | ReportActionListItemType | ReportListItemType > ) => ( ref : SelectionListHandle | null ) => {
97147 // Early return if there's no ref, new transaction wasn't brought in by this hook
98148 // or there's no new search result key
99- if ( ! ref || newSearchResultKey === null ) {
149+ if ( ! ref || ! triggeredByHookRef . current || newSearchResultKey === null ) {
100150 return ;
101151 }
102152
@@ -131,6 +181,8 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli
131181
132182 // Perform the scrolling action
133183 ref . scrollToIndex ( indexOfNewItem ) ;
184+ // Reset the trigger flag to prevent unintended future scrolls and highlights
185+ triggeredByHookRef . current = false ;
134186 } ,
135187 [ newSearchResultKey , isChat ] ,
136188 ) ;
0 commit comments