Skip to content

Commit 32b670d

Browse files
authored
Merge pull request Expensify#73740 from bernhardoj/fix/72851-only-1-expense-highlighted-after-split
Fix only one split is highlighted when splitting an expense on "Reports" > "Expenses"
2 parents 1d82ade + 124f0a7 commit 32b670d

3 files changed

Lines changed: 109 additions & 21 deletions

File tree

src/components/Search/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ function Search({
369369
openSearch();
370370
}, []);
371371

372-
const {newSearchResultKey, handleSelectionListScroll, newTransactions} = useSearchHighlightAndScroll({
372+
const {newSearchResultKeys, handleSelectionListScroll, newTransactions} = useSearchHighlightAndScroll({
373373
searchResults,
374374
transactions,
375375
previousTransactions,
@@ -762,22 +762,22 @@ function Search({
762762
: `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}`;
763763

764764
// Check if the base key matches the newSearchResultKey (TransactionListItemType)
765-
const isBaseKeyMatch = baseKey === newSearchResultKey;
765+
const isBaseKeyMatch = !!newSearchResultKeys?.has(baseKey);
766766

767767
// Check if any transaction within the transactions array (TransactionGroupListItemType) matches the newSearchResultKey
768768
const isAnyTransactionMatch =
769769
!isChat &&
770770
(item as TransactionGroupListItemType)?.transactions?.some((transaction) => {
771771
const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`;
772-
return transactionKey === newSearchResultKey;
772+
return !!newSearchResultKeys?.has(transactionKey);
773773
});
774774

775775
// Determine if either the base key or any transaction key matches
776776
const shouldAnimateInHighlight = isBaseKeyMatch || isAnyTransactionMatch;
777777

778778
return mapToItemWithAdditionalInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight, hash);
779779
}),
780-
[type, status, data, sortBy, sortOrder, validGroupBy, isChat, newSearchResultKey, selectedTransactions, canSelectMultiple, localeCompare, hash],
780+
[type, status, data, sortBy, sortOrder, validGroupBy, isChat, newSearchResultKeys, selectedTransactions, canSelectMultiple, localeCompare, hash],
781781
);
782782

783783
useEffect(() => {

src/hooks/useSearchHighlightAndScroll.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function useSearchHighlightAndScroll({
4646
const searchTriggeredRef = useRef(false);
4747
const hasNewItemsRef = useRef(false);
4848
const previousSearchResults = usePrevious(searchResults?.data);
49-
const [newSearchResultKey, setNewSearchResultKey] = useState<string | null>(null);
49+
const [newSearchResultKeys, setNewSearchResultKeys] = useState<Set<string> | null>(null);
5050
const highlightedIDs = useRef<Set<string>>(new Set());
5151
const initializedRef = useRef(false);
5252
const hasPendingSearchRef = useRef(false);
@@ -182,11 +182,13 @@ function useSearchHighlightAndScroll({
182182
return;
183183
}
184184

185-
const newReportActionID = newReportActionIDs.at(0) ?? '';
186-
const newReportActionKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newReportActionID}`;
187-
188-
setNewSearchResultKey(newReportActionKey);
189-
highlightedIDs.current.add(newReportActionID);
185+
const newKeys = new Set<string>();
186+
newReportActionIDs.forEach((id) => {
187+
const newReportActionKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${id}`;
188+
highlightedIDs.current.add(newReportActionKey);
189+
newKeys.add(newReportActionKey);
190+
});
191+
setNewSearchResultKeys(newKeys);
190192
} else {
191193
const previousTransactionIDs = extractTransactionIDsFromSearchResults(previousSearchResults);
192194
const currentTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data);
@@ -198,26 +200,28 @@ function useSearchHighlightAndScroll({
198200
return;
199201
}
200202

201-
const newTransactionID = newTransactionIDs.at(0) ?? '';
202-
const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${newTransactionID}`;
203-
204-
setNewSearchResultKey(newTransactionKey);
205-
highlightedIDs.current.add(newTransactionID);
203+
const newKeys = new Set<string>();
204+
newTransactionIDs.forEach((id) => {
205+
const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${id}`;
206+
highlightedIDs.current.add(newTransactionKey);
207+
newKeys.add(newTransactionKey);
208+
});
209+
setNewSearchResultKeys(newKeys);
206210
}
207211
}, [searchResults?.data, previousSearchResults, isChat]);
208212

209213
// Reset newSearchResultKey after it's been used
210214
useEffect(() => {
211-
if (newSearchResultKey === null) {
215+
if (newSearchResultKeys === null) {
212216
return;
213217
}
214218

215219
const timer = setTimeout(() => {
216-
setNewSearchResultKey(null);
220+
setNewSearchResultKeys(null);
217221
}, CONST.ANIMATED_HIGHLIGHT_START_DURATION);
218222

219223
return () => clearTimeout(timer);
220-
}, [newSearchResultKey]);
224+
}, [newSearchResultKeys]);
221225

222226
/**
223227
* Callback to handle scrolling to the new search result.
@@ -226,7 +230,8 @@ function useSearchHighlightAndScroll({
226230
(data: SearchListItem[], ref: SelectionListHandle | null) => {
227231
// Early return if there's no ref, new transaction wasn't brought in by this hook
228232
// or there's no new search result key
229-
if (!ref || !triggeredByHookRef.current || newSearchResultKey === null) {
233+
const newSearchResultKey = newSearchResultKeys?.values().next().value;
234+
if (!ref || !triggeredByHookRef.current || !newSearchResultKey) {
230235
return;
231236
}
232237

@@ -264,10 +269,10 @@ function useSearchHighlightAndScroll({
264269
// Reset the trigger flag to prevent unintended future scrolls and highlights
265270
triggeredByHookRef.current = false;
266271
},
267-
[newSearchResultKey, isChat],
272+
[newSearchResultKeys, isChat],
268273
);
269274

270-
return {newSearchResultKey, handleSelectionListScroll, newTransactions};
275+
return {newSearchResultKeys, handleSelectionListScroll, newTransactions};
271276
}
272277

273278
/**

tests/unit/useSearchHighlightAndScrollTest.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,87 @@ describe('useSearchHighlightAndScroll', () => {
228228
rerender(updatedProps);
229229
expect(search).not.toHaveBeenCalled();
230230
});
231+
232+
it('should return multiple new search result keys when there are multiple new expenses', () => {
233+
const {rerender, result} = renderHook((props: UseSearchHighlightAndScroll) => useSearchHighlightAndScroll(props), {
234+
initialProps: baseProps,
235+
});
236+
const updatedProps = {
237+
...baseProps,
238+
searchResults: {
239+
...baseProps.searchResults,
240+
data: {
241+
transactions_1: {
242+
transactionID: '1',
243+
},
244+
transactions_2: {
245+
transactionID: '2',
246+
},
247+
},
248+
},
249+
transactions: {
250+
'1': {transactionID: '1'},
251+
'2': {transactionID: '2'},
252+
'3': {transactionID: '3'},
253+
},
254+
previousTransactions: {
255+
'1': {transactionID: '1'},
256+
},
257+
};
258+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
259+
// @ts-expect-error
260+
rerender(updatedProps);
261+
expect(result.current.newSearchResultKeys?.size).toBe(2);
262+
});
263+
264+
it('should return multiple new search result keys when there are multiple new chats', () => {
265+
const chatProps = {
266+
...baseProps,
267+
queryJSON: {...baseProps.queryJSON, type: 'chat' as const},
268+
reportActions: {
269+
reportActions_1: {
270+
'1': {actionName: 'EXISTING', reportActionID: '1'},
271+
},
272+
},
273+
};
274+
const {rerender, result} = renderHook((props: UseSearchHighlightAndScroll) => useSearchHighlightAndScroll(props), {
275+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
276+
// @ts-expect-error
277+
initialProps: chatProps,
278+
});
279+
const updatedProps = {
280+
...chatProps,
281+
searchResults: {
282+
...baseProps.searchResults,
283+
data: {
284+
reportActions_1: {
285+
'1': {actionName: 'EXISTING', reportActionID: '1'},
286+
},
287+
reportActions_2: {
288+
'2': {actionName: 'EXISTING', reportActionID: '2'},
289+
},
290+
},
291+
},
292+
reportActions: {
293+
reportActions_1: {
294+
'1': {actionName: 'EXISTING', reportActionID: '1'},
295+
},
296+
reportActions_2: {
297+
'2': {actionName: 'EXISTING', reportActionID: '2'},
298+
},
299+
reportActions_3: {
300+
'3': {actionName: 'EXISTING', reportActionID: '3'},
301+
},
302+
},
303+
previousReportActions: {
304+
reportActions_1: {
305+
'1': {actionName: 'EXISTING', reportActionID: '1'},
306+
},
307+
},
308+
};
309+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
310+
// @ts-expect-error
311+
rerender(updatedProps);
312+
expect(result.current.newSearchResultKeys?.size).toBe(2);
313+
});
231314
});

0 commit comments

Comments
 (0)