Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/components/Search/SearchList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {PressableWithFeedback} from '@components/Pressable';
import type ChatListItem from '@components/SelectionList/ChatListItem';
import type ReportListItem from '@components/SelectionList/Search/ReportListItem';
import type TransactionListItem from '@components/SelectionList/Search/TransactionListItem';
import type {ExtendedTargetedEvent, ReportListItemType, SearchListItem} from '@components/SelectionList/types';
import type {ExtendedTargetedEvent, ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types';
import Text from '@components/Text';
import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
Expand All @@ -31,6 +31,7 @@ import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

type SearchListItem = TransactionListItemType | ReportListItemType | ReportActionListItemType;
type SearchListItemComponentType = typeof TransactionListItem | typeof ChatListItem | typeof ReportListItem;

type SearchListHandle = {
Expand Down
16 changes: 7 additions & 9 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import usePermissions from '@hooks/usePermissions';
import usePrevious from '@hooks/usePrevious';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSearchHighlightAndScroll from '@hooks/useSearchHighlightAndScroll';
import useSearchPusherUpdates from '@hooks/useSearchPusherUpdates';
import useThemeStyles from '@hooks/useThemeStyles';
import {turnOffMobileSelectionMode, turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
import {search, updateSearchResultsWithTransactionThreadReportID} from '@libs/actions/Search';
Expand Down Expand Up @@ -152,7 +151,9 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS
const {type, status, sortBy, sortOrder, hash, groupBy} = queryJSON;

const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: true});
const previousTransactions = usePrevious(transactions);
const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {canBeMissing: true});
const previousReportActions = usePrevious(reportActions);
const {translate} = useLocalize();
const shouldGroupByReports = groupBy === CONST.SEARCH.GROUP_BY.REPORTS;

Expand Down Expand Up @@ -199,17 +200,14 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS
search({queryJSON, offset});
}, [isOffline, offset, queryJSON]);

// Use custom hook to detect and handle Pusher updates
useSearchPusherUpdates({
isOffline,
queryJSON,
transactions,
reportActions,
});

const {newSearchResultKey, handleSelectionListScroll} = useSearchHighlightAndScroll({
searchResults,
transactions,
previousTransactions,
queryJSON,
offset,
reportActions,
previousReportActions,
});

// 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
Expand Down
3 changes: 0 additions & 3 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -726,8 +726,6 @@ type SectionListDataType<TItem extends ListItem> = ExtendedSectionListData<TItem

type SortableColumnName = SearchColumnType | typeof CONST.REPORT.TRANSACTION_LIST.COLUMNS.COMMENTS;

type SearchListItem = TransactionListItemType | ReportListItemType | ReportActionListItemType;

export type {
BaseListItemProps,
SelectionListProps,
Expand All @@ -750,6 +748,5 @@ export type {
UserListItemProps,
ReportActionListItemType,
ChatListItemProps,
SearchListItem,
SortableColumnName,
};
102 changes: 77 additions & 25 deletions src/hooks/useSearchHighlightAndScroll.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,80 @@
import isEqual from 'lodash/isEqual';
import {useCallback, useEffect, useRef, useState} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {SearchQueryJSON} from '@components/Search/types';
import type {ReportListItemType, SearchListItem, SelectionListHandle, TransactionListItemType} from '@components/SelectionList/types';
import type {ReportActionListItemType, ReportListItemType, SelectionListHandle, TransactionListItemType} from '@components/SelectionList/types';
import {search} from '@libs/actions/Search';
import {isReportActionEntry} from '@libs/SearchUIUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {SearchResults} from '@src/types/onyx';
import type {ReportActions, SearchResults, Transaction} from '@src/types/onyx';
import usePrevious from './usePrevious';

type UseSearchHighlightAndScroll = {
searchResults: OnyxEntry<SearchResults>;
transactions: OnyxCollection<Transaction>;
previousTransactions: OnyxCollection<Transaction>;
reportActions: OnyxCollection<ReportActions>;
previousReportActions: OnyxCollection<ReportActions>;
queryJSON: SearchQueryJSON;
offset: number;
};

/**
* Hook used to handle highlighting and scrolling for new search results.
* Hook used to trigger a search when a new transaction or report action is added and handle highlighting and scrolling.
*/
function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighlightAndScroll) {
function useSearchHighlightAndScroll({searchResults, transactions, previousTransactions, reportActions, previousReportActions, queryJSON, offset}: 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 previousSearchResults = usePrevious(searchResults?.data);
const [newSearchResultKey, setNewSearchResultKey] = useState<string | null>(null);
const highlightedIDs = useRef<Set<string>>(new Set());
const initializedRef = useRef(false);
const previousSearchResults = usePrevious(searchResults?.data);
const isChat = queryJSON.type === CONST.SEARCH.DATA_TYPES.CHAT;

// Trigger search when a new report action is added while on chat or when a new transaction is added for the other search types.
useEffect(() => {
const previousTransactionsIDs = Object.keys(previousTransactions ?? {});
const transactionsIDs = Object.keys(transactions ?? {});

const reportActionsIDs = Object.values(reportActions ?? {})
.map((actions) => Object.keys(actions ?? {}))
.flat();
const previousReportActionsIDs = Object.values(previousReportActions ?? {})
.map((actions) => Object.keys(actions ?? {}))
.flat();

if (searchTriggeredRef.current) {
return;
}
const hasTransactionsIDsChange = !isEqual(transactionsIDs, previousTransactionsIDs);
const hasReportActionsIDsChange = !isEqual(reportActionsIDs, previousReportActionsIDs);

// Check if there is a change in the transactions or report actions list
if ((!isChat && hasTransactionsIDsChange) || hasReportActionsIDsChange) {
// 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;

// Set the flag indicating the search is triggered by the hook
triggeredByHookRef.current = true;

// Trigger the search
search({queryJSON, offset});

// Set the ref to prevent further triggers until reset
searchTriggeredRef.current = true;
}

// Reset the ref when transactions or report actions in chat search type are updated
return () => {
searchTriggeredRef.current = false;
};
}, [transactions, previousTransactions, queryJSON, offset, reportActions, previousReportActions, isChat]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These dependencies were causing the expenses report page reloads after deleting an expense, we fixed this in #65153


// Initialize the set with existing IDs only once
useEffect(() => {
if (initializedRef.current || !searchResults?.data) {
Expand All @@ -34,7 +86,7 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli
initializedRef.current = true;
}, [searchResults?.data, isChat]);

// Detect new items in search results after a change in transactions or report actions
// Detect new items (transactions or report actions)
useEffect(() => {
if (!previousSearchResults || !searchResults?.data) {
return;
Expand All @@ -46,7 +98,7 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli
// 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 (newReportActionIDs.length === 0) {
if (!triggeredByHookRef.current || newReportActionIDs.length === 0 || !hasNewItemsRef.current) {
return;
}

Expand All @@ -55,25 +107,23 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli

setNewSearchResultKey(newReportActionKey);
highlightedIDs.current.add(newReportActionID);
return;
}

// For expenses/transactions
const previousTransactionIDs = extractTransactionIDsFromSearchResults(previousSearchResults);
const currentTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data);
} else {
const previousTransactionIDs = extractTransactionIDsFromSearchResults(previousSearchResults);
const currentTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data);

// Find new transaction IDs not in previous search results and not already highlighted
const newTransactionIDs = currentTransactionIDs.filter((id) => !previousTransactionIDs.includes(id) && !highlightedIDs.current.has(id));
// 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));

if (newTransactionIDs.length === 0) {
return;
}
if (!triggeredByHookRef.current || newTransactionIDs.length === 0 || !hasNewItemsRef.current) {
return;
}

const newTransactionID = newTransactionIDs.at(0) ?? '';
const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${newTransactionID}`;
const newTransactionID = newTransactionIDs.at(0) ?? '';
const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${newTransactionID}`;

setNewSearchResultKey(newTransactionKey);
highlightedIDs.current.add(newTransactionID);
setNewSearchResultKey(newTransactionKey);
highlightedIDs.current.add(newTransactionID);
}
}, [searchResults?.data, previousSearchResults, isChat]);

// Reset newSearchResultKey after it's been used
Expand All @@ -93,10 +143,10 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli
* Callback to handle scrolling to the new search result.
*/
const handleSelectionListScroll = useCallback(
(data: SearchListItem[]) => (ref: SelectionListHandle | null) => {
(data: Array<TransactionListItemType | ReportActionListItemType | ReportListItemType>) => (ref: SelectionListHandle | null) => {
// Early return if there's no ref, new transaction wasn't brought in by this hook
// or there's no new search result key
if (!ref || newSearchResultKey === null) {
if (!ref || !triggeredByHookRef.current || newSearchResultKey === null) {
return;
}

Expand Down Expand Up @@ -131,6 +181,8 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli

// Perform the scrolling action
ref.scrollToIndex(indexOfNewItem);
// Reset the trigger flag to prevent unintended future scrolls and highlights
triggeredByHookRef.current = false;
},
[newSearchResultKey, isChat],
);
Expand Down
71 changes: 0 additions & 71 deletions src/hooks/useSearchPusherUpdates.ts

This file was deleted.

Loading
Loading