From 90689fc313f1deed6410c4436e978a03170eb257 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 8 Jan 2026 12:55:52 -0700 Subject: [PATCH 01/14] use useSearchContext for current data --- src/components/MoneyReportHeader.tsx | 3 +-- src/components/ReportActionItem/MoneyRequestView.tsx | 4 +--- src/components/Search/SearchContext.tsx | 7 +++++++ src/components/Search/types.ts | 3 ++- .../Search/ExpenseReportListItem.tsx | 3 +-- .../Search/ReportListItemHeader.tsx | 3 +-- .../Search/TransactionListItem.tsx | 3 +-- src/hooks/useAllTransactions.ts | 5 +---- src/hooks/useMergeTransactions.ts | 7 ++----- src/pages/Search/SearchMoneyRequestReportPage.tsx | 3 +-- src/pages/iou/SplitExpenseCreateDateRagePage.tsx | 5 +---- src/pages/iou/SplitExpenseEditPage.tsx | 5 +---- src/pages/iou/SplitExpensePage.tsx | 4 +--- 13 files changed, 21 insertions(+), 34 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 0751d9a2e57b..eaae6f5abeb5 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -397,9 +397,8 @@ function MoneyReportHeader({ typeof CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.HOLD | typeof CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT | typeof CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT_BULK > | null>(null); - const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions, currentSearchQueryJSON, currentSearchKey, currentSearchHash} = useSearchContext(); + const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions, currentSearchQueryJSON, currentSearchKey, currentSearchHash, currentSearchResults} = useSearchContext(); const shouldCalculateTotals = useSearchShouldCalculateTotals(currentSearchKey, currentSearchQueryJSON?.similarSearchHash, true); - const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${currentSearchQueryJSON?.hash}`, {canBeMissing: true}); const {wideRHPRouteKeys} = useContext(WideRHPContext); const [network] = useOnyx(ONYXKEYS.NETWORK, {canBeMissing: true}); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 56c41e036a7b..a8a85bc1b804 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -170,9 +170,7 @@ function MoneyRequestView({ const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false}); - const searchContext = useSearchContext(); - const searchHash = searchContext?.currentSearchHash ?? CONST.DEFAULT_NUMBER_ID; - const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${searchHash}`, {canBeMissing: true}); + const {currentSearchResults} = useSearchContext(); // When this component is used when merging from the search page, we might not have the parent report stored in the main collection let [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`, {canBeMissing: true}); diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 35c0c48a8837..fb0bb0d2a154 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -1,8 +1,10 @@ import React, {useCallback, useContext, useMemo, useRef, useState} from 'react'; +import useOnyx from '@hooks/useOnyx'; import {isMoneyRequestReport} from '@libs/ReportUtils'; import {isTransactionListItemType, isTransactionReportGroupListItemType} from '@libs/SearchUIUtils'; import type {SearchKey} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {SearchContextData, SearchContextProps, SearchQueryJSON, SelectedTransactions} from './types'; @@ -11,6 +13,7 @@ const defaultSearchContextData: SearchContextData = { currentSearchHash: -1, currentSearchKey: undefined, currentSearchQueryJSON: undefined, + currentSearchResults: undefined, selectedTransactions: {}, selectedTransactionIDs: [], selectedReports: [], @@ -47,6 +50,8 @@ function SearchContextProvider({children}: ChildrenProps) { const [searchContextData, setSearchContextData] = useState(defaultSearchContextData); const areTransactionsEmpty = useRef(true); + const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${searchContextData.currentSearchHash}`, {canBeMissing: true}); + const setCurrentSearchHashAndKey = useCallback((searchHash: number, searchKey: SearchKey | undefined) => { setSearchContextData((prevState) => { if (searchHash === prevState.currentSearchHash && searchKey === prevState.currentSearchKey) { @@ -200,6 +205,7 @@ function SearchContextProvider({children}: ChildrenProps) { const searchContext = useMemo( () => ({ ...searchContextData, + currentSearchResults, removeTransaction, setCurrentSearchHashAndKey, setCurrentSearchQueryJSON, @@ -217,6 +223,7 @@ function SearchContextProvider({children}: ChildrenProps) { }), [ searchContextData, + currentSearchResults, removeTransaction, setCurrentSearchHashAndKey, setCurrentSearchQueryJSON, diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 289d2a889634..e26f802a7da4 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -3,7 +3,7 @@ import type {PaymentMethod} from '@components/KYCWall/types'; import type {ReportActionListItemType, TaskListItemType, TransactionGroupListItemType, TransactionListItemType} from '@components/SelectionListWithSections/types'; import type {SearchKey} from '@libs/SearchUIUtils'; import type CONST from '@src/CONST'; -import type {ReportAction} from '@src/types/onyx'; +import type {ReportAction, SearchResults} from '@src/types/onyx'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -125,6 +125,7 @@ type SearchContextData = { currentSearchHash: number; currentSearchKey: SearchKey | undefined; currentSearchQueryJSON: SearchQueryJSON | undefined; + currentSearchResults: SearchResults | undefined; selectedTransactions: SelectedTransactions; selectedTransactionIDs: string[]; selectedReports: SelectedReports[]; diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx index 0242a3aad707..29e2c7134124 100644 --- a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx +++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx @@ -41,10 +41,9 @@ function ExpenseReportListItem({ const theme = useTheme(); const {translate} = useLocalize(); const {isLargeScreenWidth} = useResponsiveLayout(); - const {currentSearchHash, currentSearchKey} = useSearchContext(); + const {currentSearchHash, currentSearchKey, currentSearchResults: snapshot} = useSearchContext(); const [lastPaymentMethod] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true}); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID, {canBeMissing: true}); - const [snapshot] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${currentSearchHash}`, {canBeMissing: true}); const [isActionLoading] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportItem.reportID}`, {canBeMissing: true, selector: isActionLoadingSelector}); const expensifyIcons = useMemoizedLazyExpensifyIcons(['DotIndicator']); diff --git a/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx b/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx index e013f656fe85..9119f45c9a21 100644 --- a/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx +++ b/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx @@ -211,13 +211,12 @@ function ReportListItemHeader({ const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); const theme = useTheme(); - const {currentSearchHash, currentSearchKey} = useSearchContext(); + const {currentSearchHash, currentSearchKey, currentSearchResults: snapshot} = useSearchContext(); const {isLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); const [lastPaymentMethod] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true}); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID, {canBeMissing: true}); const thereIsFromAndTo = !!reportItem?.from && !!reportItem?.to; const showUserInfo = (reportItem.type === CONST.REPORT.TYPE.IOU && thereIsFromAndTo) || (reportItem.type === CONST.REPORT.TYPE.EXPENSE && !!reportItem?.from); - const [snapshot] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${currentSearchHash}`, {canBeMissing: true}); const snapshotReport = useMemo(() => { return (snapshot?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${reportItem.reportID}`] ?? {}) as Report; }, [snapshot, reportItem.reportID]); diff --git a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx index ef398b51994a..8c41ac11f988 100644 --- a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx +++ b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx @@ -54,8 +54,7 @@ function TransactionListItem({ const theme = useTheme(); const {isLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); - const {currentSearchHash, currentSearchKey} = useSearchContext(); - const [snapshot] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${currentSearchHash}`, {canBeMissing: true}); + const {currentSearchHash, currentSearchKey, currentSearchResults: snapshot} = useSearchContext(); const snapshotReport = useMemo(() => { return (snapshot?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionItem.reportID}`] ?? {}) as Report; }, [snapshot, transactionItem.reportID]); diff --git a/src/hooks/useAllTransactions.ts b/src/hooks/useAllTransactions.ts index f907458ba3a3..34a2fb0037b5 100644 --- a/src/hooks/useAllTransactions.ts +++ b/src/hooks/useAllTransactions.ts @@ -1,7 +1,6 @@ import {useMemo} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {useSearchContext} from '@components/Search/SearchContext'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Transaction} from '@src/types/onyx'; import useOnyx from './useOnyx'; @@ -10,9 +9,7 @@ import useOnyx from './useOnyx'; * Hook that returns all transactions, filtered by current search results if a search data is available */ function useAllTransactions() { - const searchContext = useSearchContext(); - const searchHash = searchContext?.currentSearchHash ?? CONST.DEFAULT_NUMBER_ID; - const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${searchHash}`, {canBeMissing: true}); + const {currentSearchResults} = useSearchContext(); const [allTransactionsCollection] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false}); const allTransactions = useMemo(() => { diff --git a/src/hooks/useMergeTransactions.ts b/src/hooks/useMergeTransactions.ts index d33eb28b3f80..b82f1afcb742 100644 --- a/src/hooks/useMergeTransactions.ts +++ b/src/hooks/useMergeTransactions.ts @@ -2,7 +2,6 @@ import type {OnyxEntry} from 'react-native-onyx'; import {useSearchContext} from '@components/Search/SearchContext'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {getTransactionFromMergeTransaction} from '@libs/MergeTransactionUtils'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {MergeTransaction, Report, SearchResults, Transaction} from '@src/types/onyx'; import useOnyx from './useOnyx'; @@ -37,9 +36,7 @@ function getTransaction( } function useMergeTransactions({mergeTransaction}: UseMergeTransactionsProps): UseMergeTransactionsReturn { - const searchContext = useSearchContext(); - const searchHash = searchContext?.currentSearchHash ?? CONST.DEFAULT_NUMBER_ID; - const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${searchHash}`, {canBeMissing: true}); + const {currentSearchHash, currentSearchResults} = useSearchContext(); const [onyxTargetTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(mergeTransaction?.targetTransactionID)}`, { canBeMissing: true, @@ -59,7 +56,7 @@ function useMergeTransactions({mergeTransaction}: UseMergeTransactionsProps): Us }); // If we're on search and main collection reports are not available, get them from the search snapshot - if (searchHash && currentSearchResults?.data) { + if (currentSearchHash && currentSearchResults?.data) { targetTransactionReport = targetTransactionReport ?? currentSearchResults?.data[`${ONYXKEYS.COLLECTION.REPORT}${targetTransaction?.reportID}`]; sourceTransactionReport = sourceTransactionReport ?? currentSearchResults?.data[`${ONYXKEYS.COLLECTION.REPORT}${sourceTransaction?.reportID}`]; } diff --git a/src/pages/Search/SearchMoneyRequestReportPage.tsx b/src/pages/Search/SearchMoneyRequestReportPage.tsx index 7aafd088ef70..c7c05f4a3b1b 100644 --- a/src/pages/Search/SearchMoneyRequestReportPage.tsx +++ b/src/pages/Search/SearchMoneyRequestReportPage.tsx @@ -57,8 +57,7 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) { const {isOffline} = useNetwork(); const reportIDFromRoute = getNonEmptyStringOnyxID(route.params?.reportID); - const {currentSearchHash} = useSearchContext(); - const [snapshot] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${currentSearchHash}`, {canBeMissing: true}); + const {currentSearchResults: snapshot} = useSearchContext(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`, {allowStaleData: true, canBeMissing: true}); diff --git a/src/pages/iou/SplitExpenseCreateDateRagePage.tsx b/src/pages/iou/SplitExpenseCreateDateRagePage.tsx index 7cb001e41d9a..7f3fe7f29457 100644 --- a/src/pages/iou/SplitExpenseCreateDateRagePage.tsx +++ b/src/pages/iou/SplitExpenseCreateDateRagePage.tsx @@ -31,14 +31,11 @@ type SplitExpenseCreateDateRagePageProps = PlatformStackScreenProps>(() => getTransactionDetails(splitExpenseDraftTransaction) ?? {}, [splitExpenseDraftTransaction]); - - const searchHash = searchContext?.currentSearchHash ?? CONST.DEFAULT_NUMBER_ID; - const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${searchHash}`, {canBeMissing: true}); const allTransactions = useAllTransactions(); const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`]; diff --git a/src/pages/iou/SplitExpensePage.tsx b/src/pages/iou/SplitExpensePage.tsx index 450398995790..f09374236f62 100644 --- a/src/pages/iou/SplitExpensePage.tsx +++ b/src/pages/iou/SplitExpensePage.tsx @@ -69,7 +69,7 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const [errorMessage, setErrorMessage] = React.useState(''); - const searchContext = useSearchContext(); + const {currentSearchResults} = useSearchContext(); const [selectedTab] = useOnyx(`${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.SPLIT_EXPENSE_TAB_TYPE}`, {canBeMissing: true}); const [draftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, {canBeMissing: false}); @@ -78,8 +78,6 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const expenseReport = transactionReport?.type === CONST.REPORT.TYPE.EXPENSE ? transactionReport : parentTransactionReport; const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${getNonEmptyStringOnyxID(expenseReport?.policyID)}`, {canBeMissing: true}); const [expenseReportPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(expenseReport?.policyID)}`, {canBeMissing: true}); - const searchHash = searchContext?.currentSearchHash ?? CONST.DEFAULT_NUMBER_ID; - const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${searchHash}`, {canBeMissing: true}); const allTransactions = useAllTransactions(); const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`]; From 07b4235f75ba68e78afd8924bb16ac680ec5a362 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 8 Jan 2026 13:24:53 -0700 Subject: [PATCH 02/14] rm snapshot connection in SearchPageNarrow and SearchSidebar --- src/components/Navigation/SearchSidebar.tsx | 20 +++++--------------- src/pages/Search/SearchPageNarrow.tsx | 7 +------ 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/components/Navigation/SearchSidebar.tsx b/src/components/Navigation/SearchSidebar.tsx index dffea363935b..855eb1e68132 100644 --- a/src/components/Navigation/SearchSidebar.tsx +++ b/src/components/Navigation/SearchSidebar.tsx @@ -1,19 +1,15 @@ import type {ParamListBase} from '@react-navigation/native'; -import {searchResultsSelector} from '@selectors/Snapshot'; import React, {useEffect, useMemo} from 'react'; import {View} from 'react-native'; import {useSearchContext} from '@components/Search/SearchContext'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import type {PlatformStackNavigationState} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SearchFullscreenNavigatorParamList} from '@libs/Navigation/types'; import {buildSearchQueryJSON} from '@libs/SearchQueryUtils'; import SearchTypeMenu from '@pages/Search/SearchTypeMenu'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; import NavigationTabBar from './NavigationTabBar'; import NAVIGATION_TABS from './NavigationTabBar/NAVIGATION_TABS'; @@ -31,7 +27,7 @@ function SearchSidebar({state}: SearchSidebarProps) { const route = state.routes.at(-1); const params = route?.params as SearchFullscreenNavigatorParamList[typeof SCREENS.SEARCH.ROOT] | undefined; - const {lastSearchType, setLastSearchType} = useSearchContext(); + const {lastSearchType, setLastSearchType, currentSearchResults} = useSearchContext(); const queryJSON = useMemo(() => { if (!params?.q) { @@ -41,21 +37,15 @@ function SearchSidebar({state}: SearchSidebarProps) { return buildSearchQueryJSON(params.q, params.rawQuery); }, [params?.q, params?.rawQuery]); - const currentSearchResultsKey = queryJSON?.hash ?? CONST.DEFAULT_NUMBER_ID; - const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${currentSearchResultsKey}`, { - canBeMissing: true, - selector: searchResultsSelector, - }); - useEffect(() => { - if (!currentSearchResults?.type) { + if (!currentSearchResults?.search?.type) { return; } - setLastSearchType(currentSearchResults.type); - }, [lastSearchType, queryJSON, setLastSearchType, currentSearchResults?.type]); + setLastSearchType(currentSearchResults.search.type); + }, [lastSearchType, queryJSON, setLastSearchType, currentSearchResults?.search?.type]); - const shouldShowLoadingState = route?.name === SCREENS.SEARCH.MONEY_REQUEST_REPORT ? false : !isOffline && !!currentSearchResults?.isLoading; + const shouldShowLoadingState = route?.name === SCREENS.SEARCH.MONEY_REQUEST_REPORT ? false : !isOffline && !!currentSearchResults?.search?.isLoading; if (shouldUseNarrowLayout) { return null; diff --git a/src/pages/Search/SearchPageNarrow.tsx b/src/pages/Search/SearchPageNarrow.tsx index 5efc0be59900..b76f44407b9b 100644 --- a/src/pages/Search/SearchPageNarrow.tsx +++ b/src/pages/Search/SearchPageNarrow.tsx @@ -23,7 +23,6 @@ import type {BankAccountMenuItem, SearchParams, SearchQueryJSON} from '@componen import useHandleBackButton from '@hooks/useHandleBackButton'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useScrollEventEmitter from '@hooks/useScrollEventEmitter'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -36,8 +35,6 @@ import {isSearchDataLoaded} from '@libs/SearchUIUtils'; import variables from '@styles/variables'; import {searchInServer} from '@userActions/Report'; import {search} from '@userActions/Search'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {SearchResults} from '@src/types/onyx'; import type {SearchResultsInfo} from '@src/types/onyx/SearchResults'; @@ -83,8 +80,6 @@ function SearchPageNarrow({ const {clearSelectedTransactions, selectedTransactions} = useSearchContext(); const [searchRouterListVisible, setSearchRouterListVisible] = useState(false); const {isOffline} = useNetwork(); - const currentSearchResultsKey = queryJSON?.hash ?? CONST.DEFAULT_NUMBER_ID; - const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${currentSearchResultsKey}`, {canBeMissing: true}); // Controls the visibility of the educational tooltip based on user scrolling. // Hides the tooltip when the user is scrolling and displays it once scrolling stops. const triggerScrollEvent = useScrollEventEmitter(); @@ -183,7 +178,7 @@ function SearchPageNarrow({ const shouldShowFooter = !!metadata?.count || Object.keys(selectedTransactions).length > 0; const isDataLoaded = isSearchDataLoaded(searchResults, queryJSON); - const shouldShowLoadingState = !isOffline && (!isDataLoaded || !!currentSearchResults?.search?.isLoading); + const shouldShowLoadingState = !isOffline && (!isDataLoaded || !!metadata?.isLoading); return ( Date: Thu, 8 Jan 2026 13:38:20 -0700 Subject: [PATCH 03/14] lift reportActions --- .../Search/ExpenseReportListItem.tsx | 7 +++++- .../Search/ExpenseReportListItemRow.tsx | 11 ++++----- .../Search/ExportedIconCell.tsx | 23 +++---------------- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx index 29e2c7134124..4ebcb548a00c 100644 --- a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx +++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx @@ -57,6 +57,11 @@ function ExpenseReportListItem({ return (snapshotData?.[`${ONYXKEYS.COLLECTION.POLICY}${reportItem.policyID}`] ?? {}) as Policy; }, [snapshotData, reportItem.policyID]); + const reportActions = useMemo(() => { + const actionsData = snapshotData?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportItem.reportID}`]; + return actionsData ? Object.values(actionsData) : []; + }, [snapshotData, reportItem.reportID]); + const isDisabledCheckbox = useMemo(() => { const isEmpty = reportItem.transactions.length === 0; return isEmpty ?? reportItem.isDisabled ?? reportItem.isDisabledCheckbox; @@ -182,10 +187,10 @@ function ExpenseReportListItem({ {(hovered) => ( {}, onButtonPress = () => {}, isActionLoading, @@ -182,10 +182,7 @@ function ExpenseReportListItemRow({ ), [CONST.SEARCH.TABLE_COLUMNS.EXPORTED_TO]: ( - + ), [CONST.SEARCH.TABLE_COLUMNS.ACTION]: ( diff --git a/src/components/SelectionListWithSections/Search/ExportedIconCell.tsx b/src/components/SelectionListWithSections/Search/ExportedIconCell.tsx index 7e9a49c8f5e0..559ec617a5c2 100644 --- a/src/components/SelectionListWithSections/Search/ExportedIconCell.tsx +++ b/src/components/SelectionListWithSections/Search/ExportedIconCell.tsx @@ -3,38 +3,21 @@ import {View} from 'react-native'; import Avatar from '@components/Avatar'; import Icon from '@components/Icon'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; -import useOnyx from '@hooks/useOnyx'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {getOriginalMessage, isExportedToIntegrationAction} from '@libs/ReportActionsUtils'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import type {ReportAction} from '@src/types/onyx'; type ExportedIconCellProps = { - reportID?: string; - hash?: number; + reportActions?: ReportAction[]; }; -function ExportedIconCell({reportID, hash}: ExportedIconCellProps) { +function ExportedIconCell({reportActions}: ExportedIconCellProps) { const theme = useTheme(); const styles = useThemeStyles(); - // We need to subscribe directly to the snapshot to get the report actions because this can be rendered in either a group - // list (which has a separate hash than the current top-level search query) or in the top-level search query. - // This selector is specific to this edge-case (and thus is not in the selectors folder) and should be used in other places where the snapshot needs to be accessed - // eslint-disable-next-line rulesdir/no-inline-useOnyx-selector - const reportActions = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, { - canBeMissing: true, - selector: (snapshot) => { - return Object.entries(snapshot?.data ?? {}) - .filter(([key]) => key === `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`) - .map(([, value]) => Object.values(value ?? {}) as ReportAction[]) - .flat(); - }, - }); - - const actions = Object.values(reportActions[0] ?? {}); + const actions = reportActions ?? []; const icons = useMemoizedLazyExpensifyIcons(['NetSuiteSquare', 'XeroSquare', 'IntacctSquare', 'QBOSquare', 'Table', 'ZenefitsSquare', 'BillComSquare', 'CertiniaSquare']); let isExportedToCsv = false; From bda781bf8d7918127543e90ba7a05c6eb645f74d Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 8 Jan 2026 13:49:43 -0700 Subject: [PATCH 04/14] rm connection in SearchFiltersBar --- .../Search/SearchPageHeader/SearchFiltersBar.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx index 4f5fbc652ced..9aaf3d633d5a 100644 --- a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx +++ b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx @@ -1,6 +1,5 @@ import {isUserValidatedSelector} from '@selectors/Account'; import {emailSelector} from '@selectors/Session'; -import {searchResultsErrorSelector} from '@selectors/Snapshot'; import React, {useCallback, useContext, useMemo, useRef} from 'react'; import type {ReactNode} from 'react'; import {FlatList, View} from 'react-native'; @@ -90,7 +89,7 @@ function SearchFiltersBar({ const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isUserValidatedSelector, canBeMissing: true}); const [searchAdvancedFiltersForm = getEmptyObject>()] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, {canBeMissing: true}); // type, groupBy and status values are not guaranteed to respect the ts type as they come from user input - const {hash, type: unsafeType, groupBy: unsafeGroupBy, status: unsafeStatus, flatFilters} = queryJSON; + const {type: unsafeType, groupBy: unsafeGroupBy, status: unsafeStatus, flatFilters} = queryJSON; const [selectedIOUReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${currentSelectedReportID}`, {canBeMissing: true}); const isCurrentSelectedExpenseReport = isExpenseReport(currentSelectedReportID); const theme = useTheme(); @@ -102,7 +101,7 @@ function SearchFiltersBar({ const personalDetails = usePersonalDetails(); const filterFormValues = useFilterFormValues(queryJSON); const {shouldUseNarrowLayout, isLargeScreenWidth} = useResponsiveLayout(); - const {selectedTransactions, selectAllMatchingItems, areAllMatchingItemsSelected, showSelectAllMatchingItems, shouldShowFiltersBarLoading} = useSearchContext(); + const {selectedTransactions, selectAllMatchingItems, areAllMatchingItemsSelected, showSelectAllMatchingItems, shouldShowFiltersBarLoading, currentSearchResults} = useSearchContext(); const [email] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true, selector: emailSelector}); const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST, {selector: filterPersonalCards, canBeMissing: true}); @@ -111,7 +110,6 @@ function SearchFiltersBar({ const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER, {canBeMissing: true}); const [currencyList = getEmptyObject()] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); - const [searchResultsErrors] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, {canBeMissing: true, selector: searchResultsErrorSelector}); const expensifyIcons = useMemoizedLazyExpensifyIcons(['Filter', 'Columns']); const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); @@ -156,7 +154,7 @@ function SearchFiltersBar({ return workspaceOptions.filter((option) => normalizedIDs.includes(option.value)); }, [searchAdvancedFiltersForm.policyID, queryJSON.policyID, workspaceOptions]); - const hasErrors = Object.keys(searchResultsErrors ?? {}).length > 0 && !isOffline; + const hasErrors = Object.keys(currentSearchResults?.errors ?? {}).length > 0 && !isOffline; const shouldShowSelectedDropdown = headerButtonsOptions.length > 0 && (!shouldUseNarrowLayout || isMobileSelectionModeEnabled); const [typeOptions, type] = useMemo(() => { From 1fe70f6465843dde07604b529b4254a7118e77f1 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 8 Jan 2026 14:05:44 -0700 Subject: [PATCH 05/14] rename param --- .../Search/ExpenseReportListItem.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx index 4ebcb548a00c..5d7dbe67bb81 100644 --- a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx +++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx @@ -41,26 +41,26 @@ function ExpenseReportListItem({ const theme = useTheme(); const {translate} = useLocalize(); const {isLargeScreenWidth} = useResponsiveLayout(); - const {currentSearchHash, currentSearchKey, currentSearchResults: snapshot} = useSearchContext(); + const {currentSearchHash, currentSearchKey, currentSearchResults} = useSearchContext(); const [lastPaymentMethod] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true}); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID, {canBeMissing: true}); const [isActionLoading] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportItem.reportID}`, {canBeMissing: true, selector: isActionLoadingSelector}); const expensifyIcons = useMemoizedLazyExpensifyIcons(['DotIndicator']); - const snapshotData = snapshot?.data; + const searchData = currentSearchResults?.data; const snapshotReport = useMemo(() => { - return (snapshotData?.[`${ONYXKEYS.COLLECTION.REPORT}${reportItem.reportID}`] ?? {}) as Report; - }, [snapshotData, reportItem.reportID]); + return (searchData?.[`${ONYXKEYS.COLLECTION.REPORT}${reportItem.reportID}`] ?? {}) as Report; + }, [searchData, reportItem.reportID]); const snapshotPolicy = useMemo(() => { - return (snapshotData?.[`${ONYXKEYS.COLLECTION.POLICY}${reportItem.policyID}`] ?? {}) as Policy; - }, [snapshotData, reportItem.policyID]); + return (searchData?.[`${ONYXKEYS.COLLECTION.POLICY}${reportItem.policyID}`] ?? {}) as Policy; + }, [searchData, reportItem.policyID]); const reportActions = useMemo(() => { - const actionsData = snapshotData?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportItem.reportID}`]; + const actionsData = searchData?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportItem.reportID}`]; return actionsData ? Object.values(actionsData) : []; - }, [snapshotData, reportItem.reportID]); + }, [searchData, reportItem.reportID]); const isDisabledCheckbox = useMemo(() => { const isEmpty = reportItem.transactions.length === 0; From 4d00b694e613b298c36bbb472ed7f10eb769522c Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 8 Jan 2026 14:08:27 -0700 Subject: [PATCH 06/14] rename more params --- .../Search/TransactionListItem.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx index 8c41ac11f988..cfabc1cff6f7 100644 --- a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx +++ b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx @@ -54,16 +54,16 @@ function TransactionListItem({ const theme = useTheme(); const {isLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); - const {currentSearchHash, currentSearchKey, currentSearchResults: snapshot} = useSearchContext(); + const {currentSearchHash, currentSearchKey, currentSearchResults} = useSearchContext(); const snapshotReport = useMemo(() => { - return (snapshot?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionItem.reportID}`] ?? {}) as Report; - }, [snapshot, transactionItem.reportID]); + return (currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionItem.reportID}`] ?? {}) as Report; + }, [currentSearchResults, transactionItem.reportID]); const [isActionLoading] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${transactionItem.reportID}`, {canBeMissing: true, selector: isActionLoadingSelector}); const snapshotPolicy = useMemo(() => { - return (snapshot?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${transactionItem.policyID}`] ?? {}) as Policy; - }, [snapshot, transactionItem.policyID]); + return (currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${transactionItem.policyID}`] ?? {}) as Policy; + }, [currentSearchResults, transactionItem.policyID]); const [lastPaymentMethod] = useOnyx(`${ONYXKEYS.NVP_LAST_PAYMENT_METHOD}`, {canBeMissing: true}); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID, {canBeMissing: true}); From 10f3a833ba21294d374d1c657cd4be355e240106 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Fri, 9 Jan 2026 12:57:18 -0700 Subject: [PATCH 07/14] fix ts errors --- src/components/Search/SearchContext.tsx | 1 + src/components/Search/types.ts | 1 + src/components/TransactionItemRow/index.tsx | 10 +++++----- src/pages/iou/SplitExpensePage.tsx | 7 +++---- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index fb0bb0d2a154..4e227884e892 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -28,6 +28,7 @@ const defaultSearchContext: SearchContextProps = { areAllMatchingItemsSelected: false, showSelectAllMatchingItems: false, shouldShowFiltersBarLoading: false, + currentSearchResults: undefined, setLastSearchType: () => {}, setCurrentSearchHashAndKey: () => {}, setCurrentSearchQueryJSON: () => {}, diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index e26f802a7da4..c62d32d4aebb 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -135,6 +135,7 @@ type SearchContextData = { }; type SearchContextProps = SearchContextData & { + currentSearchResults: SearchResults | undefined; setCurrentSearchHashAndKey: (hash: number, key: SearchKey | undefined) => void; setCurrentSearchQueryJSON: (searchQueryJSON: SearchQueryJSON | undefined) => void; /** If you want to set `selectedTransactionIDs`, pass an array as the first argument, object/record otherwise */ diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx index 14cf550fac18..d2b1673be95a 100644 --- a/src/components/TransactionItemRow/index.tsx +++ b/src/components/TransactionItemRow/index.tsx @@ -44,7 +44,7 @@ import { } from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; -import type {PersonalDetails, Policy, Report, TransactionViolation} from '@src/types/onyx'; +import type {PersonalDetails, Policy, Report, ReportAction, TransactionViolation} from '@src/types/onyx'; import type {SearchTransactionAction} from '@src/types/onyx/SearchResults'; import CategoryCell from './DataCells/CategoryCell'; import ChatBubbleCell from './DataCells/ChatBubbleCell'; @@ -133,6 +133,7 @@ type TransactionItemRowProps = { isHover?: boolean; shouldShowArrowRightOnNarrowLayout?: boolean; customCardNames?: Record; + reportActions?: ReportAction[]; }; function getMerchantName(transactionItem: TransactionWithOptionalSearchFields, translate: (key: TranslationPaths) => string) { @@ -181,6 +182,7 @@ function TransactionItemRow({ isHover = false, shouldShowArrowRightOnNarrowLayout, customCardNames, + reportActions, }: TransactionItemRowProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -556,10 +558,7 @@ function TransactionItemRow({ ), [CONST.SEARCH.TABLE_COLUMNS.EXPORTED_TO]: ( - + ), }), @@ -592,6 +591,7 @@ function TransactionItemRow({ isTaxAmountColumnWide, isLargeScreenWidth, hash, + reportActions, ], ); const shouldRenderChatBubbleCell = useMemo(() => { diff --git a/src/pages/iou/SplitExpensePage.tsx b/src/pages/iou/SplitExpensePage.tsx index 03123e06073c..8ba780d179fa 100644 --- a/src/pages/iou/SplitExpensePage.tsx +++ b/src/pages/iou/SplitExpensePage.tsx @@ -68,8 +68,7 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const [cannotBeEditedModalVisible, setCannotBeEditedModalVisible] = useState(false); const [errorMessage, setErrorMessage] = React.useState(''); - - const {currentSearchResults} = useSearchContext(); + const searchContext = useSearchContext(); const [selectedTab] = useOnyx(`${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.SPLIT_EXPENSE_TAB_TYPE}`, {canBeMissing: true}); const [draftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, {canBeMissing: false}); @@ -86,14 +85,14 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false}); const [allReportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {canBeMissing: true}); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportID)}`, {canBeMissing: true}); - const currentReport = report ?? currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportID)}`]; + const currentReport = report ?? searchContext?.currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportID)}`]; const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); const [policyRecentlyUsedCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${getIOURequestPolicyID(transaction, currentReport)}`, {canBeMissing: true}); const policy = usePolicy(currentReport?.policyID); const currentPolicy = Object.keys(policy?.employeeList ?? {}).length ? policy - : currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(currentReport?.policyID)}`]; + : searchContext?.currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(currentReport?.policyID)}`]; const isSplitAvailable = report && transaction && isSplitAction(currentReport, [transaction], originalTransaction, currentPolicy); From 802be0aa93b4ff242b25ec4b6c0800274c92bd34 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Fri, 9 Jan 2026 13:04:17 -0700 Subject: [PATCH 08/14] fix lint --- src/components/TransactionItemRow/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx index d2b1673be95a..6b5b56336be9 100644 --- a/src/components/TransactionItemRow/index.tsx +++ b/src/components/TransactionItemRow/index.tsx @@ -590,7 +590,6 @@ function TransactionItemRow({ isAmountColumnWide, isTaxAmountColumnWide, isLargeScreenWidth, - hash, reportActions, ], ); From a89728ae70e41cf2bb8c079d497e3636d84be305 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Fri, 9 Jan 2026 13:09:04 -0700 Subject: [PATCH 09/14] rm hash --- .../Search/TransactionGroupListExpanded.tsx | 1 - src/components/TransactionItemRow/index.tsx | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx index 58fadb2469f4..3d403fde57b9 100644 --- a/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx +++ b/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx @@ -190,7 +190,6 @@ function TransactionGroupListExpanded({ const shouldShowBottomBorder = !isLastTransaction(index) && !isLargeScreenWidth; const transactionRow = ( Date: Fri, 9 Jan 2026 13:18:02 -0700 Subject: [PATCH 10/14] fix eslint --- .../SelectionListWithSections/Search/TransactionListItem.tsx | 1 - src/components/TransactionItemRow/index.tsx | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx index cfabc1cff6f7..d5fd7140e3a1 100644 --- a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx +++ b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx @@ -210,7 +210,6 @@ function TransactionListItem({ /> )} Date: Tue, 13 Jan 2026 15:02:46 -0700 Subject: [PATCH 11/14] fix exported icon cell --- .../Search/TransactionListItem.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx index d5fd7140e3a1..c9ec18ecabcf 100644 --- a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx +++ b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx @@ -64,6 +64,12 @@ function TransactionListItem({ const snapshotPolicy = useMemo(() => { return (currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${transactionItem.policyID}`] ?? {}) as Policy; }, [currentSearchResults, transactionItem.policyID]); + + const reportActions = useMemo(() => { + const actionsData = currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionItem.reportID}`]; + return actionsData ? Object.values(actionsData) : []; + }, [currentSearchResults, transactionItem.reportID]); + const [lastPaymentMethod] = useOnyx(`${ONYXKEYS.NVP_LAST_PAYMENT_METHOD}`, {canBeMissing: true}); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID, {canBeMissing: true}); @@ -232,6 +238,7 @@ function TransactionListItem({ onArrowRightPress={onPress} isHover={hovered} customCardNames={customCardNames} + reportActions={reportActions} /> )} From c47fb5e8ba18f20ea5349410ea43f67938e60d12 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 13 Jan 2026 15:07:26 -0700 Subject: [PATCH 12/14] rename param --- .../SelectionListWithSections/Search/TransactionListItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx index c9ec18ecabcf..caaf524bd56c 100644 --- a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx +++ b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx @@ -65,7 +65,7 @@ function TransactionListItem({ return (currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${transactionItem.policyID}`] ?? {}) as Policy; }, [currentSearchResults, transactionItem.policyID]); - const reportActions = useMemo(() => { + const exportedReportActions = useMemo(() => { const actionsData = currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionItem.reportID}`]; return actionsData ? Object.values(actionsData) : []; }, [currentSearchResults, transactionItem.reportID]); @@ -238,7 +238,7 @@ function TransactionListItem({ onArrowRightPress={onPress} isHover={hovered} customCardNames={customCardNames} - reportActions={reportActions} + reportActions={exportedReportActions} /> )} From d4d681226cbb4205cd65327ccf3c1e3bcf86dd2e Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 13 Jan 2026 15:27:46 -0700 Subject: [PATCH 13/14] fix test --- tests/unit/hooks/useAllTransactions.test.ts | 86 +++++++-------------- 1 file changed, 26 insertions(+), 60 deletions(-) diff --git a/tests/unit/hooks/useAllTransactions.test.ts b/tests/unit/hooks/useAllTransactions.test.ts index c278c43f9d79..0b4b323d6e79 100644 --- a/tests/unit/hooks/useAllTransactions.test.ts +++ b/tests/unit/hooks/useAllTransactions.test.ts @@ -6,11 +6,11 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {SearchResults} from '@src/types/onyx'; import createRandomTransaction from '../../utils/collections/transaction'; -const mockCurrentSearchHash = 12345; +let mockCurrentSearchResults: SearchResults | undefined; jest.mock('@components/Search/SearchContext', () => ({ useSearchContext: () => ({ - currentSearchHash: mockCurrentSearchHash, + currentSearchResults: mockCurrentSearchResults, }), })); @@ -24,6 +24,7 @@ describe('useAllTransactions', () => { beforeEach(() => { jest.clearAllMocks(); Onyx.clear(); + mockCurrentSearchResults = undefined; }); it('should return all transactions from collection when no search results', async () => { @@ -52,7 +53,7 @@ describe('useAllTransactions', () => { transaction1.transactionID = 'txn1'; await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}txn1`, transaction1); - await Onyx.merge(`${ONYXKEYS.COLLECTION.SNAPSHOT}${mockCurrentSearchHash}`, { + mockCurrentSearchResults = { search: { offset: 0, type: CONST.SEARCH.DATA_TYPES.EXPENSE, @@ -62,7 +63,7 @@ describe('useAllTransactions', () => { isLoading: false, }, data: undefined, - }); + } as unknown as SearchResults; const {result} = renderHook(() => useAllTransactions()); @@ -81,7 +82,7 @@ describe('useAllTransactions', () => { searchTransaction.transactionID = 'searchTxn1'; collectionTransaction.transactionID = 'collectionTxn1'; - await Onyx.merge(`${ONYXKEYS.COLLECTION.SNAPSHOT}${mockCurrentSearchHash}`, { + mockCurrentSearchResults = { search: { offset: 0, type: CONST.SEARCH.DATA_TYPES.EXPENSE, @@ -93,7 +94,7 @@ describe('useAllTransactions', () => { data: { [`${ONYXKEYS.COLLECTION.TRANSACTION}searchTxn1`]: searchTransaction, }, - } as unknown as SearchResults); + } as unknown as SearchResults; await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}collectionTxn1`, collectionTransaction); const {result} = renderHook(() => useAllTransactions()); @@ -117,7 +118,7 @@ describe('useAllTransactions', () => { searchTransaction.amount = 1000; collectionTransaction.amount = 2000; - await Onyx.merge(`${ONYXKEYS.COLLECTION.SNAPSHOT}${mockCurrentSearchHash}`, { + mockCurrentSearchResults = { search: { offset: 0, type: CONST.SEARCH.DATA_TYPES.EXPENSE, @@ -129,7 +130,7 @@ describe('useAllTransactions', () => { data: { [`${ONYXKEYS.COLLECTION.TRANSACTION}txn1`]: searchTransaction, }, - } as unknown as SearchResults); + } as unknown as SearchResults; await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}txn1`, collectionTransaction); const {result} = renderHook(() => useAllTransactions()); @@ -146,7 +147,7 @@ describe('useAllTransactions', () => { const transaction = createRandomTransaction(1); transaction.transactionID = 'txn1'; - await Onyx.merge(`${ONYXKEYS.COLLECTION.SNAPSHOT}${mockCurrentSearchHash}`, { + mockCurrentSearchResults = { search: { offset: 0, type: CONST.SEARCH.DATA_TYPES.EXPENSE, @@ -159,7 +160,7 @@ describe('useAllTransactions', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION}txn1`]: transaction, [`${ONYXKEYS.COLLECTION.REPORT}report1`]: {reportID: 'report1'}, }, - } as unknown as SearchResults); + } as unknown as SearchResults; const {result} = renderHook(() => useAllTransactions()); @@ -175,7 +176,7 @@ describe('useAllTransactions', () => { }); it('should handle empty collection and empty search results', async () => { - await Onyx.merge(`${ONYXKEYS.COLLECTION.SNAPSHOT}${mockCurrentSearchHash}`, { + mockCurrentSearchResults = { search: { offset: 0, type: CONST.SEARCH.DATA_TYPES.EXPENSE, @@ -185,7 +186,7 @@ describe('useAllTransactions', () => { isLoading: false, }, data: {}, - }); + } as unknown as SearchResults; const {result} = renderHook(() => useAllTransactions()); @@ -200,7 +201,7 @@ describe('useAllTransactions', () => { const transaction = createRandomTransaction(1); transaction.transactionID = 'txn1'; - await Onyx.merge(`${ONYXKEYS.COLLECTION.SNAPSHOT}${mockCurrentSearchHash}`, { + mockCurrentSearchResults = { search: { offset: 0, type: CONST.SEARCH.DATA_TYPES.EXPENSE, @@ -214,7 +215,7 @@ describe('useAllTransactions', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION}txn2`]: null, [`${ONYXKEYS.COLLECTION.TRANSACTION}txn3`]: undefined, }, - } as unknown as SearchResults); + } as unknown as SearchResults; const {result} = renderHook(() => useAllTransactions()); @@ -256,11 +257,16 @@ describe('useAllTransactions', () => { }); }); - it('should update when search results change', async () => { + it('should return search transactions when search results are available', async () => { + // This test verifies that search results transactions are returned + // Note: Since the mock is static, we can't test reactive updates to search results + // Reactive updates are tested via the collection transactions test const transaction1 = createRandomTransaction(1); + const transaction2 = createRandomTransaction(2); transaction1.transactionID = 'txn1'; + transaction2.transactionID = 'txn2'; - await Onyx.merge(`${ONYXKEYS.COLLECTION.SNAPSHOT}${mockCurrentSearchHash}`, { + mockCurrentSearchResults = { search: { offset: 0, type: CONST.SEARCH.DATA_TYPES.EXPENSE, @@ -271,36 +277,14 @@ describe('useAllTransactions', () => { }, data: { [`${ONYXKEYS.COLLECTION.TRANSACTION}txn1`]: transaction1, + [`${ONYXKEYS.COLLECTION.TRANSACTION}txn2`]: transaction2, }, - } as unknown as SearchResults); + } as unknown as SearchResults; const {result} = renderHook(() => useAllTransactions()); await waitFor(() => { expect(result.current).toBeDefined(); - expect(Object.keys(result.current ?? {}).length).toBe(1); - }); - - // Update search results - const transaction2 = createRandomTransaction(2); - transaction2.transactionID = 'txn2'; - - await Onyx.merge(`${ONYXKEYS.COLLECTION.SNAPSHOT}${mockCurrentSearchHash}`, { - search: { - offset: 0, - type: CONST.SEARCH.DATA_TYPES.EXPENSE, - status: CONST.SEARCH.STATUS.EXPENSE.ALL, - hasMoreResults: false, - hasResults: true, - isLoading: false, - }, - data: { - [`${ONYXKEYS.COLLECTION.TRANSACTION}txn1`]: transaction1, - [`${ONYXKEYS.COLLECTION.TRANSACTION}txn2`]: transaction2, - }, - } as unknown as SearchResults); - - await waitFor(() => { expect(Object.keys(result.current ?? {}).length).toBe(2); }); @@ -310,30 +294,12 @@ describe('useAllTransactions', () => { }); }); - it('should use default search hash when context hash is not available', async () => { + it('should return collection transactions when search results are undefined', async () => { const transaction = createRandomTransaction(1); transaction.transactionID = 'txn1'; - const searchHash = CONST.DEFAULT_NUMBER_ID; - - // Mock context without search hash - jest.doMock('@components/Search/SearchContext', () => ({ - useSearchContext: () => ({ - currentSearchHash: undefined, - }), - })); + // Keep mockCurrentSearchResults as undefined (set in beforeEach) await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}txn1`, transaction); - await Onyx.merge(`${ONYXKEYS.COLLECTION.SNAPSHOT}${searchHash}`, { - search: { - offset: 0, - type: CONST.SEARCH.DATA_TYPES.EXPENSE, - status: CONST.SEARCH.STATUS.EXPENSE.ALL, - hasMoreResults: false, - hasResults: false, - isLoading: false, - }, - data: {}, - }); const {result} = renderHook(() => useAllTransactions()); From 3b52f84f76cb5fe5f1a5d05c9e1a5d0a4ea2c7b7 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 14 Jan 2026 12:05:19 -0700 Subject: [PATCH 14/14] fix expanded group --- .../Search/TransactionGroupListExpanded.tsx | 3 +++ .../Search/TransactionGroupListItem.tsx | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx index 3d403fde57b9..afbe31305f33 100644 --- a/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx +++ b/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx @@ -188,6 +188,8 @@ function TransactionGroupListExpanded({ )} {visibleTransactions.map((transaction, index) => { const shouldShowBottomBorder = !isLastTransaction(index) && !isLargeScreenWidth; + const exportedReportActions = Object.values(transactionsSnapshot?.data?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction?.reportID}`] ?? {}); + const transactionRow = ( ({ shouldShowBottomBorder={shouldShowBottomBorder} onArrowRightPress={() => openReportInRHP(transaction)} shouldShowArrowRightOnNarrowLayout + reportActions={exportedReportActions} /> ); return ( diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx index c369b094c74b..75dca66dc055 100644 --- a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx +++ b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx @@ -324,9 +324,7 @@ function TransactionGroupListItem({ onExpandIconPress, isFocused, searchType, - groupBy, isDisabled, - onDEWModalOpen, onSelectRow, transactionPreviewData, ],