From 01f7b7cbb92879f4c6c28d124894919ebf3286c1 Mon Sep 17 00:00:00 2001
From: DylanDylann <dylandylan9112@gmail.com>
Date: Mon, 10 Nov 2025 18:21:32 +0700
Subject: [PATCH 01/11] refactor isActionLoading

---
 .../MoneyRequestReportNavigation.tsx          |  18 +--
 src/components/Search/index.tsx               |  15 ++-
 .../Search/ReportListItemHeader.tsx           |   3 +-
 .../Search/TransactionGroupListItem.tsx       |  14 ++-
 .../Search/TransactionListItem.tsx            |   4 +-
 .../Search/UserInfoAndActionButtonRow.tsx     |  13 +--
 src/libs/SearchUIUtils.ts                     |  42 ++++---
 src/libs/actions/Search.ts                    | 109 ++++++++++++------
 src/types/onyx/ReportMetadata.ts              |   3 +
 src/types/onyx/SearchResults.ts               |   3 -
 tests/unit/Search/SearchUIUtilsTest.ts        |  97 +++++++++++++---
 11 files changed, 227 insertions(+), 94 deletions(-)

diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
index c9c44726d3d5..3979a2f50ae3 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
@@ -25,6 +25,7 @@ function MoneyRequestReportNavigation({reportID, shouldDisplayNarrowVersion}: Mo
     const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${lastSearchQuery?.queryJSON?.hash}`, {canBeMissing: true});
     const currentUserDetails = useCurrentUserPersonalDetails();
     const {localeCompare, formatPhoneNumber} = useLocalize();
+    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true});
 
     const [exportReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {
         canEvict: false,
@@ -37,17 +38,18 @@ function MoneyRequestReportNavigation({reportID, shouldDisplayNarrowVersion}: Mo
     const {type, status, sortBy, sortOrder, groupBy} = lastSearchQuery?.queryJSON ?? {};
     let results: Array<string | undefined> = [];
     if (!!type && !!currentSearchResults?.data && !!currentSearchResults?.search) {
-        const searchData = getSections(
+        const searchData = getSections({
             type,
-            currentSearchResults.data,
-            currentUserDetails.accountID,
-            currentUserDetails.email ?? '',
+            data: currentSearchResults.data,
+            currentAccountID: currentUserDetails.accountID,
+            currentUserEmail: currentUserDetails.email ?? '',
             formatPhoneNumber,
             groupBy,
-            exportReportActions,
-            lastSearchQuery?.searchKey,
-            archivedReportsIdSet,
-        );
+            reportActions: exportReportActions,
+            currentSearch: lastSearchQuery?.searchKey,
+            archivedReportsIDList: archivedReportsIdSet,
+            reportMetadata,
+        });
         results = getSortedSections(type, status ?? '', searchData, localeCompare, sortBy, sortOrder, groupBy).map((value) => value.reportID);
     }
     const allReports = results;
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index 9ae0110fd7bd..dc38e81b60fa 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -257,6 +257,7 @@ function Search({
     const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID, {canBeMissing: true});
     const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});
     const {accountID, email} = useCurrentUserPersonalDetails();
+    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true});
 
     // Filter violations based on user visibility
     const filteredViolations = useMemo(() => {
@@ -405,7 +406,19 @@ function Search({
             return [[], 0];
         }
 
-        const data1 = getSections(type, searchResults.data, accountID, email ?? '', formatPhoneNumber, validGroupBy, exportReportActions, searchKey, archivedReportsIdSet, queryJSON);
+        const data1 = getSections({
+            type,
+            data: searchResults.data,
+            currentAccountID: accountID,
+            currentUserEmail: email ?? '',
+            formatPhoneNumber,
+            groupBy: validGroupBy,
+            reportActions: exportReportActions,
+            currentSearch: searchKey,
+            archivedReportsIDList: archivedReportsIdSet,
+            queryJSON,
+            reportMetadata,
+        });
         return [data1, data1.length];
     }, [searchKey, exportReportActions, validGroupBy, isDataLoaded, searchResults, type, archivedReportsIdSet, formatPhoneNumber, accountID, queryJSON, email]);
 
diff --git a/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx b/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx
index b59158413b9b..ab5e8699c2a6 100644
--- a/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx
+++ b/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx
@@ -108,6 +108,7 @@ function HeaderFirstRow<TItem extends ListItem>({
     const StyleUtils = useStyleUtils();
     const {isLargeScreenWidth} = useResponsiveLayout();
     const theme = useTheme();
+    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportItem.reportID}`, {canBeMissing: true});
 
     const {total, currency} = useMemo(() => {
         let reportTotal = reportItem.total ?? 0;
@@ -180,7 +181,7 @@ function HeaderFirstRow<TItem extends ListItem>({
                         action={reportItem.action}
                         goToItem={handleOnButtonPress}
                         isSelected={reportItem.isSelected}
-                        isLoading={reportItem.isActionLoading}
+                        isLoading={reportMetadata?.isActionLoading}
                         policyID={reportItem.policyID}
                         reportID={reportItem.reportID}
                         hash={reportItem.hash}
diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
index a74a1f396fc2..996af80f9979 100644
--- a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
@@ -94,6 +94,7 @@ function TransactionGroupListItem<TItem extends ListItem>({
     const isExpenseReportType = searchType === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT;
     const [transactionsVisibleLimit, setTransactionsVisibleLimit] = useState(CONST.TRANSACTION.RESULTS_PAGE_SIZE as number);
     const [isExpanded, setIsExpanded] = useState(false);
+    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true});
 
     const transactions = useMemo(() => {
         if (isExpenseReportType) {
@@ -102,13 +103,14 @@ function TransactionGroupListItem<TItem extends ListItem>({
         if (!transactionsSnapshot?.data) {
             return [];
         }
-        const sectionData = getSections(
-            CONST.SEARCH.DATA_TYPES.EXPENSE,
-            transactionsSnapshot?.data,
-            accountID,
-            currentUserDetails.email ?? '',
+        const sectionData = getSections({
+            type: CONST.SEARCH.DATA_TYPES.EXPENSE,
+            data: transactionsSnapshot?.data,
+            currentAccountID: accountID,
+            currentUserEmail: currentUserDetails.email ?? '',
             formatPhoneNumber,
-        ) as TransactionListItemType[];
+            reportMetadata,
+        }) as TransactionListItemType[];
         return sectionData.map((transactionItem) => ({
             ...transactionItem,
             isSelected: selectedTransactionIDsSet.has(transactionItem.transactionID),
diff --git a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
index 701c7295fa54..168b56495c95 100644
--- a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
@@ -58,6 +58,8 @@ function TransactionListItem<TItem extends ListItem>({
         return (snapshot?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionItem.reportID}`] ?? {}) as SearchReport;
     }, [snapshot, transactionItem.reportID]);
 
+    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${transactionItem.reportID}`, {canBeMissing: true});
+
     const snapshotPolicy = useMemo(() => {
         return (snapshot?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${transactionItem.policyID}`] ?? {}) as Policy;
     }, [snapshot, transactionItem.policyID]);
@@ -189,7 +191,7 @@ function TransactionListItem<TItem extends ListItem>({
                     onCheckboxPress={handleCheckboxPress}
                     shouldUseNarrowLayout={!isLargeScreenWidth}
                     columns={columns}
-                    isActionLoading={isLoading ?? transactionItem.isActionLoading ?? snapshotReport.isActionLoading}
+                    isActionLoading={isLoading ?? transactionItem.isActionLoading ?? reportMetadata.isActionLoading}
                     isSelected={!!transactionItem.isSelected}
                     dateColumnSize={dateColumnSize}
                     amountColumnSize={amountColumnSize}
diff --git a/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx b/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
index 2478ea68e336..4a2b29fb8f58 100644
--- a/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
+++ b/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
@@ -1,7 +1,6 @@
-import React, {useMemo} from 'react';
+import React from 'react';
 import {View} from 'react-native';
 import type {StyleProp, ViewStyle} from 'react-native';
-import {useSearchContext} from '@components/Search/SearchContext';
 import type {TransactionListItemType, TransactionReportGroupListItemType} from '@components/SelectionListWithSections/types';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
@@ -11,7 +10,6 @@ import {isCorrectSearchUserName} from '@libs/SearchUIUtils';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import type {SearchReport} from '@src/types/onyx/SearchResults';
 import ActionCell from './ActionCell';
 import UserInfoCellsWithArrow from './UserInfoCellsWithArrow';
 
@@ -30,18 +28,13 @@ function UserInfoAndActionButtonRow({
     const {isLargeScreenWidth} = useResponsiveLayout();
     const {translate} = useLocalize();
     const transactionItem = item as unknown as TransactionListItemType;
-    const {currentSearchHash} = useSearchContext();
-    const [snapshot] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${currentSearchHash}`, {canBeMissing: true});
+    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${transactionItem.reportID}`, {canBeMissing: true});
     const hasFromSender = !!item?.from && !!item?.from?.accountID && !!item?.from?.displayName;
     const hasToRecipient = !!item?.to && !!item?.to?.accountID && !!item?.to?.displayName;
     const participantFromDisplayName = item?.from?.displayName ?? item?.from?.login ?? translate('common.hidden');
     const participantToDisplayName = item?.to?.displayName ?? item?.to?.login ?? translate('common.hidden');
     const shouldShowToRecipient = hasFromSender && hasToRecipient && !!item?.to?.accountID && !!isCorrectSearchUserName(participantToDisplayName);
 
-    const snapshotReport = useMemo(() => {
-        return (snapshot?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionItem.reportID}`] ?? {}) as SearchReport;
-    }, [snapshot, transactionItem.reportID]);
-
     return (
         <View
             style={[
@@ -74,7 +67,7 @@ function UserInfoAndActionButtonRow({
                     action={item.action}
                     goToItem={handleActionButtonPress}
                     isSelected={item.isSelected}
-                    isLoading={item.isActionLoading ?? snapshotReport.isActionLoading}
+                    isLoading={item.isActionLoading ?? reportMetadata.isActionLoading}
                     policyID={item.policyID}
                     reportID={item.reportID}
                     hash={item.hash}
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index 46c064a4f996..6d6e5c404bb5 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -294,6 +294,20 @@ type SearchDateModifierLower = Lowercase<SearchDateModifier>;
 
 type ArchivedReportsIDSet = ReadonlySet<string>;
 
+type GetSectionsParams = {
+    type: SearchDataTypes;
+    data: OnyxTypes.SearchResults['data'];
+    currentAccountID: number | undefined;
+    currentUserEmail: string;
+    formatPhoneNumber: LocaleContextProps['formatPhoneNumber'];
+    groupBy?: SearchGroupBy;
+    reportActions?: Record<string, OnyxTypes.ReportAction[]>;
+    currentSearch?: SearchKey;
+    archivedReportsIDList?: ArchivedReportsIDSet;
+    queryJSON?: SearchQueryJSON;
+    reportMetadata?: Record<string, OnyxTypes.ReportMetadata | undefined> | undefined;
+};
+
 /**
  * Returns a list of all possible searches in the LHN, along with their query & hash.
  * *NOTE* When rendering the LHN, you should use the "createTypeMenuSections" method, which
@@ -1392,6 +1406,7 @@ function getReportSections(
     currentUserEmail: string,
     formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
     reportActions: Record<string, OnyxTypes.ReportAction[]> = {},
+    reportMetadata: OnyxTypes.ReportMetadata | undefined = undefined,
 ): TransactionGroupListItemType[] {
     const shouldShowMerchant = getShouldShowMerchant(data);
 
@@ -1428,7 +1443,7 @@ function getReportSections(
             const actions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportItem.reportID}`];
 
             let shouldShow = true;
-            if (queryJSON && !reportItem.isActionLoading) {
+            if (queryJSON && reportMetadata && !reportMetadata[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportItem.reportID}`]?.isActionLoading) {
                 if (queryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE) {
                     const status = queryJSON.status;
 
@@ -1634,18 +1649,19 @@ function getListItem(type: SearchDataTypes, status: SearchStatus, groupBy?: Sear
 /**
  * Organizes data into appropriate list sections for display based on the type of search results.
  */
-function getSections(
-    type: SearchDataTypes,
-    data: OnyxTypes.SearchResults['data'],
-    currentAccountID: number | undefined,
-    currentUserEmail: string,
-    formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
-    groupBy?: SearchGroupBy,
-    reportActions: Record<string, OnyxTypes.ReportAction[]> = {},
-    currentSearch: SearchKey = CONST.SEARCH.SEARCH_KEYS.EXPENSES,
-    archivedReportsIDList?: ArchivedReportsIDSet,
-    queryJSON?: SearchQueryJSON,
-) {
+function getSections({
+    type,
+    data,
+    currentAccountID,
+    currentUserEmail,
+    formatPhoneNumber,
+    groupBy,
+    reportActions = {},
+    currentSearch = CONST.SEARCH.SEARCH_KEYS.EXPENSES,
+    archivedReportsIDList,
+    queryJSON,
+    reportMetadata,
+}: GetSectionsParams) {
     if (type === CONST.SEARCH.DATA_TYPES.CHAT) {
         return getReportActionsSections(data);
     }
diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index 5b2eb1471591..02fe08e830c2 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -454,18 +454,30 @@ function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], com
 // eslint-disable-next-line @typescript-eslint/no-deprecated
 function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], policy: Policy[], transactionIDList?: string[], currentSearchKey?: SearchKey) {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<SearchReport> | null): OnyxUpdate[] => [
-        {
-            onyxMethod: Onyx.METHOD.MERGE,
-            key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
-            value: {
-                data: transactionIDList
-                    ? (Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>)
-                    : // eslint-disable-next-line @typescript-eslint/no-deprecated
-                      (Object.fromEntries(reportList.map((report) => [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, update])) as Partial<SearchReport>),
+    const createOnyxData = (update: Partial<SearchTransaction> | Partial<SearchReport> | null): OnyxUpdate[] => {
+        const optimisticData: OnyxUpdate[] = [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
+                value: {
+                    data: transactionIDList
+                        ? (Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>)
+                        : undefined,
+                },
             },
-        },
-    ];
+        ];
+        reportList.forEach((report) => {
+            optimisticData.push({
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`,
+                value: {
+                    ...update,
+                },
+            });
+        });
+
+        return optimisticData;
+    };
 
     const optimisticData: OnyxUpdate[] = createOnyxData({isActionLoading: true});
     const failureData: OnyxUpdate[] = createOnyxData({isActionLoading: false});
@@ -487,18 +499,30 @@ function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], po
 
 function approveMoneyRequestOnSearch(hash: number, reportIDList: string[], transactionIDList?: string[], currentSearchKey?: SearchKey) {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<SearchReport> | null): OnyxUpdate[] => [
-        {
-            onyxMethod: Onyx.METHOD.MERGE,
-            key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
-            value: {
-                data: transactionIDList
-                    ? (Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>)
-                    : // eslint-disable-next-line @typescript-eslint/no-deprecated
-                      (Object.fromEntries(reportIDList.map((reportID) => [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, update])) as Partial<SearchReport>),
+    const createOnyxData = (update: Partial<SearchTransaction> | Partial<SearchReport> | null): OnyxUpdate[] => {
+        const optimisticData: OnyxUpdate[] = [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
+                value: {
+                    data: transactionIDList
+                        ? (Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>)
+                        : undefined,
+                },
             },
-        },
-    ];
+        ];
+        reportIDList.forEach((reportID) => {
+            optimisticData.push({
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`,
+                value: {
+                    ...update,
+                },
+            });
+        });
+
+        return optimisticData;
+    };
 
     const optimisticData: OnyxUpdate[] = createOnyxData({isActionLoading: true});
     const failureData: OnyxUpdate[] = createOnyxData({isActionLoading: false, errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')});
@@ -522,11 +546,9 @@ function exportToIntegrationOnSearch(hash: number, reportID: string, connectionN
         // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830
         {
             onyxMethod: Onyx.METHOD.MERGE,
-            key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
+            key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`,
             value: {
-                data: {
-                    [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: update,
-                },
+                ...update,
             },
         },
         {
@@ -557,18 +579,31 @@ function exportToIntegrationOnSearch(hash: number, reportID: string, connectionN
 
 function payMoneyRequestOnSearch(hash: number, paymentData: PaymentData[], transactionIDList?: string[], currentSearchKey?: SearchKey) {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<SearchReport> | null): OnyxUpdate[] => [
-        {
-            onyxMethod: Onyx.METHOD.MERGE,
-            key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
-            value: {
-                data: transactionIDList
-                    ? (Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>)
-                    : // eslint-disable-next-line @typescript-eslint/no-deprecated
-                      (Object.fromEntries(paymentData.map((item) => [`${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, update])) as Partial<SearchReport>),
+    const createOnyxData = (update: Partial<SearchTransaction> | Partial<SearchReport> | null): OnyxUpdate[] => {
+        const optimisticData: OnyxUpdate[] = [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
+                value: {
+                    data: transactionIDList
+                        ? (Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>)
+                        : undefined,
+                },
             },
-        },
-    ];
+        ];
+
+        paymentData.forEach((item) => {
+            optimisticData.push({
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${item.reportID}`,
+                value: {
+                    ...update,
+                },
+            });
+        });
+
+        return optimisticData;
+    };
 
     const optimisticData: OnyxUpdate[] = createOnyxData({isActionLoading: true});
     const failureData: OnyxUpdate[] = createOnyxData({isActionLoading: false, errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')});
diff --git a/src/types/onyx/ReportMetadata.ts b/src/types/onyx/ReportMetadata.ts
index 2c9e0e9f0d2b..6d55dd0ec5e0 100644
--- a/src/types/onyx/ReportMetadata.ts
+++ b/src/types/onyx/ReportMetadata.ts
@@ -43,6 +43,9 @@ type ReportMetadata = {
 
     /** Pending members of the report */
     pendingChatMembers?: PendingChatMember[];
+
+    /** Whether the action is loading */
+    isActionLoading?: boolean;
 };
 
 export default ReportMetadata;
diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts
index de1adab7c3ce..5f4e9b7f5458 100644
--- a/src/types/onyx/SearchResults.ts
+++ b/src/types/onyx/SearchResults.ts
@@ -139,9 +139,6 @@ type SearchReport = {
     /** For expense reports, this is the total amount requested */
     unheldTotal?: number;
 
-    /** Whether the action is loading */
-    isActionLoading?: boolean;
-
     /** Whether the report has violations or errors */
     errors?: OnyxCommon.Errors;
 
diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts
index a21f27c56f8a..f53a70abdae0 100644
--- a/tests/unit/Search/SearchUIUtilsTest.ts
+++ b/tests/unit/Search/SearchUIUtilsTest.ts
@@ -1803,11 +1803,27 @@ describe('SearchUIUtils', () => {
 
     describe('Test getSections', () => {
         it('should return getReportActionsSections result when type is CHAT', () => {
-            expect(SearchUIUtils.getSections(CONST.SEARCH.DATA_TYPES.CHAT, searchResults.data, 2074551, '', formatPhoneNumber)).toStrictEqual(reportActionListItems);
+            expect(
+                SearchUIUtils.getSections({
+                    type: CONST.SEARCH.DATA_TYPES.CHAT,
+                    data: searchResults.data,
+                    currentAccountID: 2074551,
+                    currentUserEmail: '',
+                    formatPhoneNumber,
+                }),
+            ).toStrictEqual(reportActionListItems);
         });
 
         it('should return getTransactionsSections result when groupBy is undefined', () => {
-            expect(SearchUIUtils.getSections(CONST.SEARCH.DATA_TYPES.EXPENSE, searchResults.data, 20745, '', formatPhoneNumber)).toEqual(transactionsListItems);
+            expect(
+                SearchUIUtils.getSections({
+                    type: CONST.SEARCH.DATA_TYPES.EXPENSE,
+                    data: searchResults.data,
+                    currentAccountID: 20745,
+                    currentUserEmail: '',
+                    formatPhoneNumber,
+                }),
+            ).toEqual(transactionsListItems);
         });
 
         it('should include iouRequestType property for distance transactions', () => {
@@ -1825,7 +1841,13 @@ describe('SearchUIUtils', () => {
                 },
             };
 
-            const result = SearchUIUtils.getSections(CONST.SEARCH.DATA_TYPES.EXPENSE, testSearchResults.data, 2074551, '', formatPhoneNumber) as TransactionListItemType[];
+            const result = SearchUIUtils.getSections({
+                type: CONST.SEARCH.DATA_TYPES.EXPENSE,
+                data: testSearchResults.data,
+                currentAccountID: 2074551,
+                currentUserEmail: '',
+                formatPhoneNumber,
+            }) as TransactionListItemType[];
 
             const distanceTransaction = result.find((item) => item.transactionID === distanceTransactionID);
 
@@ -1851,7 +1873,13 @@ describe('SearchUIUtils', () => {
                 },
             };
 
-            const result = SearchUIUtils.getSections(CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT, testSearchResults.data, 2074551, '', formatPhoneNumber) as TransactionGroupListItemType[];
+            const result = SearchUIUtils.getSections({
+                type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
+                data: testSearchResults.data,
+                currentAccountID: 2074551,
+                currentUserEmail: '',
+                formatPhoneNumber,
+            }) as TransactionGroupListItemType[];
 
             const reportGroup = result.find((group) => group.transactions?.some((transaction) => transaction.transactionID === distanceTransactionID));
 
@@ -1865,7 +1893,15 @@ describe('SearchUIUtils', () => {
         });
 
         it('should return getReportSections result when type is EXPENSE REPORT', () => {
-            expect(SearchUIUtils.getSections(CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT, searchResults.data, 2074551, '', formatPhoneNumber)).toStrictEqual(transactionReportGroupListItems);
+            expect(
+                SearchUIUtils.getSections({
+                    type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
+                    data: searchResults.data,
+                    currentAccountID: 2074551,
+                    currentUserEmail: '',
+                    formatPhoneNumber,
+                }),
+            ).toStrictEqual(transactionReportGroupListItems);
         });
 
         it('should handle data where transaction keys appear before report keys in getReportSections', () => {
@@ -1893,8 +1929,20 @@ describe('SearchUIUtils', () => {
                 [`policy_${policyID}`]: searchResults.data[`policy_${policyID}`],
             };
 
-            const resultTransactionFirst = SearchUIUtils.getSections(CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT, testDataTransactionFirst, 2074551, '', formatPhoneNumber);
-            const resultReportFirst = SearchUIUtils.getSections(CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT, testDataReportFirst, 2074551, '', formatPhoneNumber);
+            const resultTransactionFirst = SearchUIUtils.getSections({
+                type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
+                data: testDataTransactionFirst,
+                currentAccountID: 2074551,
+                currentUserEmail: '',
+                formatPhoneNumber,
+            });
+            const resultReportFirst = SearchUIUtils.getSections({
+                type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
+                data: testDataReportFirst,
+                currentAccountID: 2074551,
+                currentUserEmail: '',
+                formatPhoneNumber,
+            });
 
             expect(resultTransactionFirst).toBeDefined();
             expect(Array.isArray(resultTransactionFirst)).toBe(true);
@@ -1907,20 +1955,41 @@ describe('SearchUIUtils', () => {
         });
 
         it('should return getMemberSections result when type is EXPENSE and groupBy is from', () => {
-            expect(SearchUIUtils.getSections(CONST.SEARCH.DATA_TYPES.EXPENSE, searchResultsGroupByFrom.data, 2074551, '', formatPhoneNumber, CONST.SEARCH.GROUP_BY.FROM)).toStrictEqual(
-                transactionMemberGroupListItems,
-            );
+            expect(
+                SearchUIUtils.getSections({
+                    type: CONST.SEARCH.DATA_TYPES.EXPENSE,
+                    data: searchResultsGroupByFrom.data,
+                    currentAccountID: 2074551,
+                    currentUserEmail: '',
+                    formatPhoneNumber,
+                    groupBy: CONST.SEARCH.GROUP_BY.FROM,
+                }),
+            ).toStrictEqual(transactionMemberGroupListItems);
         });
 
         it('should return getCardSections result when type is EXPENSE and groupBy is card', () => {
-            expect(SearchUIUtils.getSections(CONST.SEARCH.DATA_TYPES.EXPENSE, searchResultsGroupByCard.data, 2074551, '', formatPhoneNumber, CONST.SEARCH.GROUP_BY.CARD)).toStrictEqual(
-                transactionCardGroupListItems,
-            );
+            expect(
+                SearchUIUtils.getSections({
+                    type: CONST.SEARCH.DATA_TYPES.EXPENSE,
+                    data: searchResultsGroupByCard.data,
+                    currentAccountID: 2074551,
+                    currentUserEmail: '',
+                    formatPhoneNumber,
+                    groupBy: CONST.SEARCH.GROUP_BY.CARD,
+                }),
+            ).toStrictEqual(transactionCardGroupListItems);
         });
 
         it('should return getWithdrawalIDSections result when type is EXPENSE and groupBy is withdrawal-id', () => {
             expect(
-                SearchUIUtils.getSections(CONST.SEARCH.DATA_TYPES.EXPENSE, searchResultsGroupByWithdrawalID.data, 2074551, '', formatPhoneNumber, CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID),
+                SearchUIUtils.getSections({
+                    type: CONST.SEARCH.DATA_TYPES.EXPENSE,
+                    data: searchResultsGroupByWithdrawalID.data,
+                    currentAccountID: 2074551,
+                    currentUserEmail: '',
+                    formatPhoneNumber,
+                    groupBy: CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID,
+                }),
             ).toStrictEqual(transactionWithdrawalIDGroupListItems);
         });
     });

From 558c5586d833e1fdfb8f7efccbaeacac9a161680 Mon Sep 17 00:00:00 2001
From: DylanDylann <dylandylan9112@gmail.com>
Date: Mon, 10 Nov 2025 18:44:25 +0700
Subject: [PATCH 02/11] fix typecheck

---
 src/components/Search/index.tsx                       |  2 +-
 .../Search/TransactionGroupListItem.tsx               |  2 +-
 .../Search/TransactionListItem.tsx                    |  2 +-
 .../Search/UserInfoAndActionButtonRow.tsx             |  2 +-
 src/libs/SearchUIUtils.ts                             |  4 ++--
 src/libs/actions/Search.ts                            | 11 +++++------
 src/types/onyx/ReportMetadata.ts                      |  3 +++
 7 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index dc38e81b60fa..57da50b900eb 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -420,7 +420,7 @@ function Search({
             reportMetadata,
         });
         return [data1, data1.length];
-    }, [searchKey, exportReportActions, validGroupBy, isDataLoaded, searchResults, type, archivedReportsIdSet, formatPhoneNumber, accountID, queryJSON, email]);
+    }, [searchKey, exportReportActions, validGroupBy, isDataLoaded, searchResults, type, archivedReportsIdSet, formatPhoneNumber, accountID, queryJSON, email, reportMetadata]);
 
     useEffect(() => {
         /** We only want to display the skeleton for the status filters the first time we load them for a specific data type */
diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
index 996af80f9979..656c22282922 100644
--- a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
@@ -115,7 +115,7 @@ function TransactionGroupListItem<TItem extends ListItem>({
             ...transactionItem,
             isSelected: selectedTransactionIDsSet.has(transactionItem.transactionID),
         }));
-    }, [isExpenseReportType, transactionsSnapshot?.data, accountID, formatPhoneNumber, groupItem.transactions, selectedTransactionIDsSet, currentUserDetails.email]);
+    }, [isExpenseReportType, transactionsSnapshot?.data, accountID, formatPhoneNumber, groupItem.transactions, selectedTransactionIDsSet, currentUserDetails.email, reportMetadata]);
 
     const selectedItemsLength = useMemo(() => {
         return transactions.reduce((acc, transaction) => {
diff --git a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
index 168b56495c95..baf9c5997a00 100644
--- a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
@@ -191,7 +191,7 @@ function TransactionListItem<TItem extends ListItem>({
                     onCheckboxPress={handleCheckboxPress}
                     shouldUseNarrowLayout={!isLargeScreenWidth}
                     columns={columns}
-                    isActionLoading={isLoading ?? transactionItem.isActionLoading ?? reportMetadata.isActionLoading}
+                    isActionLoading={isLoading ?? transactionItem.isActionLoading ?? reportMetadata?.isActionLoading}
                     isSelected={!!transactionItem.isSelected}
                     dateColumnSize={dateColumnSize}
                     amountColumnSize={amountColumnSize}
diff --git a/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx b/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
index 4a2b29fb8f58..8798a53ab73b 100644
--- a/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
+++ b/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
@@ -67,7 +67,7 @@ function UserInfoAndActionButtonRow({
                     action={item.action}
                     goToItem={handleActionButtonPress}
                     isSelected={item.isSelected}
-                    isLoading={item.isActionLoading ?? reportMetadata.isActionLoading}
+                    isLoading={item.isActionLoading ?? reportMetadata?.isActionLoading}
                     policyID={item.policyID}
                     reportID={item.reportID}
                     hash={item.hash}
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index 6d6e5c404bb5..03323892ec3b 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -1406,7 +1406,7 @@ function getReportSections(
     currentUserEmail: string,
     formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
     reportActions: Record<string, OnyxTypes.ReportAction[]> = {},
-    reportMetadata: OnyxTypes.ReportMetadata | undefined = undefined,
+    reportMetadata: Record<string, OnyxTypes.ReportMetadata | undefined> | undefined;
 ): TransactionGroupListItemType[] {
     const shouldShowMerchant = getShouldShowMerchant(data);
 
@@ -1670,7 +1670,7 @@ function getSections({
     }
 
     if (type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT) {
-        return getReportSections(data, currentSearch, currentAccountID, currentUserEmail, formatPhoneNumber, reportActions);
+        return getReportSections(data, currentSearch, currentAccountID, currentUserEmail, formatPhoneNumber, reportActions, reportMetadata);
     }
 
     if (groupBy) {
diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index 02fe08e830c2..3eabec49d65d 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -41,7 +41,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import {FILTER_KEYS} from '@src/types/form/SearchAdvancedFiltersForm';
 import type {SearchAdvancedFiltersForm} from '@src/types/form/SearchAdvancedFiltersForm';
-import type {ExportTemplate, LastPaymentMethod, LastPaymentMethodType, Policy, ReportAction, ReportActions, Transaction} from '@src/types/onyx';
+import type {ExportTemplate, LastPaymentMethod, LastPaymentMethodType, Policy, ReportAction, ReportActions, ReportMetadata, Transaction} from '@src/types/onyx';
 import type {PaymentInformation} from '@src/types/onyx/LastPaymentMethod';
 import type {ConnectionName} from '@src/types/onyx/Policy';
 import type {SearchReport, SearchTransaction} from '@src/types/onyx/SearchResults';
@@ -454,7 +454,7 @@ function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], com
 // eslint-disable-next-line @typescript-eslint/no-deprecated
 function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], policy: Policy[], transactionIDList?: string[], currentSearchKey?: SearchKey) {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<SearchReport> | null): OnyxUpdate[] => {
+    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null): OnyxUpdate[] => {
         const optimisticData: OnyxUpdate[] = [
             {
                 onyxMethod: Onyx.METHOD.MERGE,
@@ -499,7 +499,7 @@ function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], po
 
 function approveMoneyRequestOnSearch(hash: number, reportIDList: string[], transactionIDList?: string[], currentSearchKey?: SearchKey) {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<SearchReport> | null): OnyxUpdate[] => {
+    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null): OnyxUpdate[] => {
         const optimisticData: OnyxUpdate[] = [
             {
                 onyxMethod: Onyx.METHOD.MERGE,
@@ -541,8 +541,7 @@ function exportToIntegrationOnSearch(hash: number, reportID: string, connectionN
     const successAction: OptimisticExportIntegrationAction = {...optimisticAction, pendingAction: null};
     const optimisticReportActionID = optimisticAction.reportActionID;
 
-    // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<SearchReport> | null, reportAction?: OptimisticExportIntegrationAction | null): OnyxUpdate[] => [
+    const createOnyxData = (update: Partial<ReportMetadata> | null, reportAction?: OptimisticExportIntegrationAction | null): OnyxUpdate[] => [
         // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830
         {
             onyxMethod: Onyx.METHOD.MERGE,
@@ -579,7 +578,7 @@ function exportToIntegrationOnSearch(hash: number, reportID: string, connectionN
 
 function payMoneyRequestOnSearch(hash: number, paymentData: PaymentData[], transactionIDList?: string[], currentSearchKey?: SearchKey) {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<SearchReport> | null): OnyxUpdate[] => {
+    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null): OnyxUpdate[] => {
         const optimisticData: OnyxUpdate[] = [
             {
                 onyxMethod: Onyx.METHOD.MERGE,
diff --git a/src/types/onyx/ReportMetadata.ts b/src/types/onyx/ReportMetadata.ts
index 6d55dd0ec5e0..a006bd90c5b2 100644
--- a/src/types/onyx/ReportMetadata.ts
+++ b/src/types/onyx/ReportMetadata.ts
@@ -46,6 +46,9 @@ type ReportMetadata = {
 
     /** Whether the action is loading */
     isActionLoading?: boolean;
+
+    /** Whether the report has violations or errors */
+    errors?: OnyxCommon.Errors;
 };
 
 export default ReportMetadata;

From ab08a1db00beecb15220074366e0541825c6edec Mon Sep 17 00:00:00 2001
From: DylanDylann <dylandylan9112@gmail.com>
Date: Mon, 10 Nov 2025 18:49:20 +0700
Subject: [PATCH 03/11] minor fix

---
 .../Search/UserInfoAndActionButtonRow.tsx                       | 2 +-
 src/libs/SearchUIUtils.ts                                       | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx b/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
index 8798a53ab73b..00bef24af8c9 100644
--- a/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
+++ b/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
@@ -67,7 +67,7 @@ function UserInfoAndActionButtonRow({
                     action={item.action}
                     goToItem={handleActionButtonPress}
                     isSelected={item.isSelected}
-                    isLoading={item.isActionLoading ?? reportMetadata?.isActionLoading}
+                    isLoading={item?.isActionLoading ?? reportMetadata?.isActionLoading}
                     policyID={item.policyID}
                     reportID={item.reportID}
                     hash={item.hash}
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index 03323892ec3b..64963b78973f 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -1406,7 +1406,7 @@ function getReportSections(
     currentUserEmail: string,
     formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
     reportActions: Record<string, OnyxTypes.ReportAction[]> = {},
-    reportMetadata: Record<string, OnyxTypes.ReportMetadata | undefined> | undefined;
+    reportMetadata: Record<string, OnyxTypes.ReportMetadata | undefined> | undefined,
 ): TransactionGroupListItemType[] {
     const shouldShowMerchant = getShouldShowMerchant(data);
 

From 58d5077df5e98968e008bb7820e5322aab6dcf3b Mon Sep 17 00:00:00 2001
From: DylanDylann <dylandylan9112@gmail.com>
Date: Mon, 10 Nov 2025 19:02:37 +0700
Subject: [PATCH 04/11] chore: eslint

---
 .../Search/UserInfoAndActionButtonRow.tsx                     | 4 ++--
 src/libs/SearchUIUtils.ts                                     | 4 ++--
 src/libs/actions/Search.ts                                    | 1 -
 3 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx b/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
index 00bef24af8c9..0e17f779dab5 100644
--- a/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
+++ b/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
@@ -34,7 +34,7 @@ function UserInfoAndActionButtonRow({
     const participantFromDisplayName = item?.from?.displayName ?? item?.from?.login ?? translate('common.hidden');
     const participantToDisplayName = item?.to?.displayName ?? item?.to?.login ?? translate('common.hidden');
     const shouldShowToRecipient = hasFromSender && hasToRecipient && !!item?.to?.accountID && !!isCorrectSearchUserName(participantToDisplayName);
-
+    const isLoading = 'isActionLoading' in item ? item?.isActionLoading : reportMetadata?.isActionLoading;
     return (
         <View
             style={[
@@ -67,7 +67,7 @@ function UserInfoAndActionButtonRow({
                     action={item.action}
                     goToItem={handleActionButtonPress}
                     isSelected={item.isSelected}
-                    isLoading={item?.isActionLoading ?? reportMetadata?.isActionLoading}
+                    isLoading={isLoading}
                     policyID={item.policyID}
                     reportID={item.reportID}
                     hash={item.hash}
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index 64963b78973f..03b8406a40c9 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -1405,8 +1405,8 @@ function getReportSections(
     currentAccountID: number | undefined,
     currentUserEmail: string,
     formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
-    reportActions: Record<string, OnyxTypes.ReportAction[]> = {},
     reportMetadata: Record<string, OnyxTypes.ReportMetadata | undefined> | undefined,
+    reportActions: Record<string, OnyxTypes.ReportAction[]> = {},
 ): TransactionGroupListItemType[] {
     const shouldShowMerchant = getShouldShowMerchant(data);
 
@@ -1670,7 +1670,7 @@ function getSections({
     }
 
     if (type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT) {
-        return getReportSections(data, currentSearch, currentAccountID, currentUserEmail, formatPhoneNumber, reportActions, reportMetadata);
+        return getReportSections(data, currentSearch, currentAccountID, currentUserEmail, formatPhoneNumber, reportMetadata, reportActions);
     }
 
     if (groupBy) {
diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index 3eabec49d65d..f2a2f54a4ba3 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -542,7 +542,6 @@ function exportToIntegrationOnSearch(hash: number, reportID: string, connectionN
     const optimisticReportActionID = optimisticAction.reportActionID;
 
     const createOnyxData = (update: Partial<ReportMetadata> | null, reportAction?: OptimisticExportIntegrationAction | null): OnyxUpdate[] => [
-        // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830
         {
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`,

From 366db854f836d52e9ffd264d035990444f9026d6 Mon Sep 17 00:00:00 2001
From: DylanDylann <dylandylan9112@gmail.com>
Date: Mon, 10 Nov 2025 23:45:44 +0700
Subject: [PATCH 05/11] add selector

---
 .../MoneyRequestReportView/MoneyRequestReportNavigation.tsx | 3 ++-
 src/components/Search/index.tsx                             | 3 ++-
 .../Search/TransactionGroupListItem.tsx                     | 3 ++-
 src/selectors/ReportMetaData.ts                             | 6 ++++++
 4 files changed, 12 insertions(+), 3 deletions(-)
 create mode 100644 src/selectors/ReportMetaData.ts

diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
index 3979a2f50ae3..e8cf2b784305 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
@@ -14,6 +14,7 @@ import {saveLastSearchParams} from '@userActions/ReportNavigation';
 import {search} from '@userActions/Search';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
+import {isActionLoadingSelector} from '@src/selectors/ReportMetaData';
 
 type MoneyRequestReportNavigationProps = {
     reportID?: string;
@@ -25,7 +26,7 @@ function MoneyRequestReportNavigation({reportID, shouldDisplayNarrowVersion}: Mo
     const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${lastSearchQuery?.queryJSON?.hash}`, {canBeMissing: true});
     const currentUserDetails = useCurrentUserPersonalDetails();
     const {localeCompare, formatPhoneNumber} = useLocalize();
-    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true});
+    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true, selector: isActionLoadingSelector});
 
     const [exportReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {
         canEvict: false,
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index 57da50b900eb..80d0847f4787 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -64,6 +64,7 @@ import NAVIGATORS from '@src/NAVIGATORS';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import SCREENS from '@src/SCREENS';
+import {isActionLoadingSelector} from '@src/selectors/ReportMetaData';
 import type {OutstandingReportsByPolicyIDDerivedValue} from '@src/types/onyx';
 import type Policy from '@src/types/onyx/Policy';
 import type Report from '@src/types/onyx/Report';
@@ -257,7 +258,7 @@ function Search({
     const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID, {canBeMissing: true});
     const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});
     const {accountID, email} = useCurrentUserPersonalDetails();
-    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true});
+    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true, selector: isActionLoadingSelector});
 
     // Filter violations based on user visibility
     const filteredViolations = useMemo(() => {
diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
index 656c22282922..20880b9e693d 100644
--- a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
@@ -35,6 +35,7 @@ import {getSections} from '@libs/SearchUIUtils';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
+import {isActionLoadingSelector} from '@src/selectors/ReportMetaData';
 import type {ReportAction, ReportActions} from '@src/types/onyx';
 import CardListItemHeader from './CardListItemHeader';
 import MemberListItemHeader from './MemberListItemHeader';
@@ -94,7 +95,7 @@ function TransactionGroupListItem<TItem extends ListItem>({
     const isExpenseReportType = searchType === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT;
     const [transactionsVisibleLimit, setTransactionsVisibleLimit] = useState(CONST.TRANSACTION.RESULTS_PAGE_SIZE as number);
     const [isExpanded, setIsExpanded] = useState(false);
-    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true});
+    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true, selector: isActionLoadingSelector});
 
     const transactions = useMemo(() => {
         if (isExpenseReportType) {
diff --git a/src/selectors/ReportMetaData.ts b/src/selectors/ReportMetaData.ts
new file mode 100644
index 000000000000..fea3a344f9eb
--- /dev/null
+++ b/src/selectors/ReportMetaData.ts
@@ -0,0 +1,6 @@
+import type {OnyxEntry} from 'react-native-onyx';
+import type {ReportMetadata} from '@src/types/onyx';
+
+const isActionLoadingSelector = (reportMetadata: OnyxEntry<ReportMetadata> | undefined) => reportMetadata?.isActionLoading;
+
+export {isActionLoadingSelector};

From c0da99585e75c727827d5e21574def55b9bc1ebe Mon Sep 17 00:00:00 2001
From: DylanDylann <dylandylan9112@gmail.com>
Date: Mon, 10 Nov 2025 23:59:34 +0700
Subject: [PATCH 06/11] fix eslint

---
 .../MoneyRequestReportView/MoneyRequestReportNavigation.tsx     | 2 +-
 src/components/Search/index.tsx                                 | 2 +-
 .../Search/TransactionGroupListItem.tsx                         | 2 +-
 src/selectors/ReportMetaData.ts                                 | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
index e8cf2b784305..b98f5347d6db 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
@@ -14,7 +14,7 @@ import {saveLastSearchParams} from '@userActions/ReportNavigation';
 import {search} from '@userActions/Search';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import {isActionLoadingSelector} from '@src/selectors/ReportMetaData';
+import isActionLoadingSelector from '@src/selectors/ReportMetaData';
 
 type MoneyRequestReportNavigationProps = {
     reportID?: string;
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index 80d0847f4787..19a4dab9fa20 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -64,7 +64,7 @@ import NAVIGATORS from '@src/NAVIGATORS';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import SCREENS from '@src/SCREENS';
-import {isActionLoadingSelector} from '@src/selectors/ReportMetaData';
+import isActionLoadingSelector from '@src/selectors/ReportMetaData';
 import type {OutstandingReportsByPolicyIDDerivedValue} from '@src/types/onyx';
 import type Policy from '@src/types/onyx/Policy';
 import type Report from '@src/types/onyx/Report';
diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
index 20880b9e693d..11296e0c845f 100644
--- a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
@@ -35,7 +35,7 @@ import {getSections} from '@libs/SearchUIUtils';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import {isActionLoadingSelector} from '@src/selectors/ReportMetaData';
+import isActionLoadingSelector from '@src/selectors/ReportMetaData';
 import type {ReportAction, ReportActions} from '@src/types/onyx';
 import CardListItemHeader from './CardListItemHeader';
 import MemberListItemHeader from './MemberListItemHeader';
diff --git a/src/selectors/ReportMetaData.ts b/src/selectors/ReportMetaData.ts
index fea3a344f9eb..aad3cd372cfa 100644
--- a/src/selectors/ReportMetaData.ts
+++ b/src/selectors/ReportMetaData.ts
@@ -3,4 +3,4 @@ import type {ReportMetadata} from '@src/types/onyx';
 
 const isActionLoadingSelector = (reportMetadata: OnyxEntry<ReportMetadata> | undefined) => reportMetadata?.isActionLoading;
 
-export {isActionLoadingSelector};
+export default isActionLoadingSelector;

From 254a44242db3449754f4744f6c0bdebe3e34c546 Mon Sep 17 00:00:00 2001
From: DylanDylann <dylandylan9112@gmail.com>
Date: Tue, 11 Nov 2025 00:32:27 +0700
Subject: [PATCH 07/11] update selector

---
 .../MoneyRequestReportView/MoneyRequestReportNavigation.tsx  | 3 +--
 src/components/Search/index.tsx                              | 3 +--
 .../Search/ReportListItemHeader.tsx                          | 5 +++--
 .../Search/TransactionGroupListItem.tsx                      | 3 +--
 .../SelectionListWithSections/Search/TransactionListItem.tsx | 5 +++--
 .../Search/UserInfoAndActionButtonRow.tsx                    | 5 +++--
 src/selectors/ReportMetaData.ts                              | 2 +-
 7 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
index b98f5347d6db..3979a2f50ae3 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
@@ -14,7 +14,6 @@ import {saveLastSearchParams} from '@userActions/ReportNavigation';
 import {search} from '@userActions/Search';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import isActionLoadingSelector from '@src/selectors/ReportMetaData';
 
 type MoneyRequestReportNavigationProps = {
     reportID?: string;
@@ -26,7 +25,7 @@ function MoneyRequestReportNavigation({reportID, shouldDisplayNarrowVersion}: Mo
     const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${lastSearchQuery?.queryJSON?.hash}`, {canBeMissing: true});
     const currentUserDetails = useCurrentUserPersonalDetails();
     const {localeCompare, formatPhoneNumber} = useLocalize();
-    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true, selector: isActionLoadingSelector});
+    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true});
 
     const [exportReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {
         canEvict: false,
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index 19a4dab9fa20..57da50b900eb 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -64,7 +64,6 @@ import NAVIGATORS from '@src/NAVIGATORS';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import SCREENS from '@src/SCREENS';
-import isActionLoadingSelector from '@src/selectors/ReportMetaData';
 import type {OutstandingReportsByPolicyIDDerivedValue} from '@src/types/onyx';
 import type Policy from '@src/types/onyx/Policy';
 import type Report from '@src/types/onyx/Report';
@@ -258,7 +257,7 @@ function Search({
     const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID, {canBeMissing: true});
     const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});
     const {accountID, email} = useCurrentUserPersonalDetails();
-    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true, selector: isActionLoadingSelector});
+    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true});
 
     // Filter violations based on user visibility
     const filteredViolations = useMemo(() => {
diff --git a/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx b/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx
index 01ce19ede3c4..54fe3aaad446 100644
--- a/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx
+++ b/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx
@@ -16,6 +16,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
 import {handleActionButtonPress} from '@userActions/Search';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
+import isActionLoadingSelector from '@src/selectors/ReportMetaData';
 import type {Policy} from '@src/types/onyx';
 import type {SearchReport} from '@src/types/onyx/SearchResults';
 import ActionCell from './ActionCell';
@@ -108,7 +109,7 @@ function HeaderFirstRow<TItem extends ListItem>({
     const StyleUtils = useStyleUtils();
     const {isLargeScreenWidth} = useResponsiveLayout();
     const theme = useTheme();
-    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportItem.reportID}`, {canBeMissing: true});
+    const [isActionLoading] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportItem.reportID}`, {canBeMissing: true, selector: isActionLoadingSelector});
 
     const {total, currency} = useMemo(() => {
         let reportTotal = reportItem.total ?? 0;
@@ -181,7 +182,7 @@ function HeaderFirstRow<TItem extends ListItem>({
                         action={reportItem.action}
                         goToItem={handleOnButtonPress}
                         isSelected={reportItem.isSelected}
-                        isLoading={reportMetadata?.isActionLoading}
+                        isLoading={isActionLoading}
                         policyID={reportItem.policyID}
                         reportID={reportItem.reportID}
                         hash={reportItem.hash}
diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
index 11296e0c845f..656c22282922 100644
--- a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
@@ -35,7 +35,6 @@ import {getSections} from '@libs/SearchUIUtils';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import isActionLoadingSelector from '@src/selectors/ReportMetaData';
 import type {ReportAction, ReportActions} from '@src/types/onyx';
 import CardListItemHeader from './CardListItemHeader';
 import MemberListItemHeader from './MemberListItemHeader';
@@ -95,7 +94,7 @@ function TransactionGroupListItem<TItem extends ListItem>({
     const isExpenseReportType = searchType === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT;
     const [transactionsVisibleLimit, setTransactionsVisibleLimit] = useState(CONST.TRANSACTION.RESULTS_PAGE_SIZE as number);
     const [isExpanded, setIsExpanded] = useState(false);
-    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true, selector: isActionLoadingSelector});
+    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true});
 
     const transactions = useMemo(() => {
         if (isExpenseReportType) {
diff --git a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
index aba315ac03d7..7a36c7988529 100644
--- a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
@@ -24,6 +24,7 @@ import {isViolationDismissed, shouldShowViolation} from '@libs/TransactionUtils'
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
+import isActionLoadingSelector from '@src/selectors/ReportMetaData';
 import type {Policy, ReportAction, ReportActions} from '@src/types/onyx';
 import type {SearchReport} from '@src/types/onyx/SearchResults';
 import type {TransactionViolation} from '@src/types/onyx/TransactionViolation';
@@ -58,7 +59,7 @@ function TransactionListItem<TItem extends ListItem>({
         return (snapshot?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionItem.reportID}`] ?? {}) as SearchReport;
     }, [snapshot, transactionItem.reportID]);
 
-    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${transactionItem.reportID}`, {canBeMissing: true});
+    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;
@@ -178,7 +179,7 @@ function TransactionListItem<TItem extends ListItem>({
                     onCheckboxPress={handleCheckboxPress}
                     shouldUseNarrowLayout={!isLargeScreenWidth}
                     columns={columns}
-                    isActionLoading={isLoading ?? transactionItem.isActionLoading ?? reportMetadata?.isActionLoading}
+                    isActionLoading={isLoading ?? transactionItem.isActionLoading ?? isActionLoading}
                     isSelected={!!transactionItem.isSelected}
                     dateColumnSize={dateColumnSize}
                     amountColumnSize={amountColumnSize}
diff --git a/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx b/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
index 61838a8fdcb3..0a995fa9e0a9 100644
--- a/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
+++ b/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
@@ -10,6 +10,7 @@ import {isCorrectSearchUserName} from '@libs/SearchUIUtils';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
+import isActionLoadingSelector from '@src/selectors/ReportMetaData';
 import ActionCell from './ActionCell';
 import UserInfoCellsWithArrow from './UserInfoCellsWithArrow';
 
@@ -30,13 +31,13 @@ function UserInfoAndActionButtonRow({
     const {isLargeScreenWidth} = useResponsiveLayout();
     const {translate} = useLocalize();
     const transactionItem = item as unknown as TransactionListItemType;
-    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${transactionItem.reportID}`, {canBeMissing: true});
+    const [isActionLoading] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${transactionItem.reportID}`, {canBeMissing: true, selector: isActionLoadingSelector});
     const hasFromSender = !!item?.from && !!item?.from?.accountID && !!item?.from?.displayName;
     const hasToRecipient = !!item?.to && !!item?.to?.accountID && !!item?.to?.displayName;
     const participantFromDisplayName = item?.from?.displayName ?? item?.from?.login ?? translate('common.hidden');
     const participantToDisplayName = item?.to?.displayName ?? item?.to?.login ?? translate('common.hidden');
     const shouldShowToRecipient = hasFromSender && hasToRecipient && !!item?.to?.accountID && !!isCorrectSearchUserName(participantToDisplayName);
-    const isLoading = 'isActionLoading' in item ? item?.isActionLoading : reportMetadata?.isActionLoading;
+    const isLoading = 'isActionLoading' in item ? item?.isActionLoading : isActionLoading;
     return (
         <View
             style={[
diff --git a/src/selectors/ReportMetaData.ts b/src/selectors/ReportMetaData.ts
index aad3cd372cfa..5e4f45a05b1a 100644
--- a/src/selectors/ReportMetaData.ts
+++ b/src/selectors/ReportMetaData.ts
@@ -1,6 +1,6 @@
 import type {OnyxEntry} from 'react-native-onyx';
 import type {ReportMetadata} from '@src/types/onyx';
 
-const isActionLoadingSelector = (reportMetadata: OnyxEntry<ReportMetadata> | undefined) => reportMetadata?.isActionLoading;
+const isActionLoadingSelector = (reportMetadata: OnyxEntry<ReportMetadata> | undefined) => reportMetadata?.isActionLoading ?? false;
 
 export default isActionLoadingSelector;

From 75cae2bac1837795f5b072b0abf017adcf3d023d Mon Sep 17 00:00:00 2001
From: DylanDylann <dylandylan9112@gmail.com>
Date: Tue, 11 Nov 2025 16:21:13 +0700
Subject: [PATCH 08/11] add selector

---
 .../MoneyRequestReportNavigation.tsx           |  5 +++--
 src/components/Search/index.tsx                |  7 ++++---
 .../Search/TransactionGroupListItem.tsx        |  7 ++++---
 src/libs/SearchUIUtils.ts                      | 10 +++++-----
 src/selectors/ReportMetaData.ts                | 18 ++++++++++++++++--
 5 files changed, 32 insertions(+), 15 deletions(-)

diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
index 3979a2f50ae3..b7d6290ec7ac 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
@@ -14,6 +14,7 @@ import {saveLastSearchParams} from '@userActions/ReportNavigation';
 import {search} from '@userActions/Search';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
+import {isActionLoadingSetSelector} from '@src/selectors/ReportMetaData';
 
 type MoneyRequestReportNavigationProps = {
     reportID?: string;
@@ -25,7 +26,7 @@ function MoneyRequestReportNavigation({reportID, shouldDisplayNarrowVersion}: Mo
     const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${lastSearchQuery?.queryJSON?.hash}`, {canBeMissing: true});
     const currentUserDetails = useCurrentUserPersonalDetails();
     const {localeCompare, formatPhoneNumber} = useLocalize();
-    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true});
+    const [isActionLoadingSet = new Set<string>()] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true, selector: isActionLoadingSetSelector});
 
     const [exportReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {
         canEvict: false,
@@ -48,7 +49,7 @@ function MoneyRequestReportNavigation({reportID, shouldDisplayNarrowVersion}: Mo
             reportActions: exportReportActions,
             currentSearch: lastSearchQuery?.searchKey,
             archivedReportsIDList: archivedReportsIdSet,
-            reportMetadata,
+            isActionLoadingSet,
         });
         results = getSortedSections(type, status ?? '', searchData, localeCompare, sortBy, sortOrder, groupBy).map((value) => value.reportID);
     }
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index 57da50b900eb..2c4de12e33ce 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -64,6 +64,7 @@ import NAVIGATORS from '@src/NAVIGATORS';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import SCREENS from '@src/SCREENS';
+import {isActionLoadingSetSelector} from '@src/selectors/ReportMetaData';
 import type {OutstandingReportsByPolicyIDDerivedValue} from '@src/types/onyx';
 import type Policy from '@src/types/onyx/Policy';
 import type Report from '@src/types/onyx/Report';
@@ -257,7 +258,7 @@ function Search({
     const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID, {canBeMissing: true});
     const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});
     const {accountID, email} = useCurrentUserPersonalDetails();
-    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true});
+    const [isActionLoadingSet = new Set<string>()] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true, selector: isActionLoadingSetSelector});
 
     // Filter violations based on user visibility
     const filteredViolations = useMemo(() => {
@@ -417,10 +418,10 @@ function Search({
             currentSearch: searchKey,
             archivedReportsIDList: archivedReportsIdSet,
             queryJSON,
-            reportMetadata,
+            isActionLoadingSet,
         });
         return [data1, data1.length];
-    }, [searchKey, exportReportActions, validGroupBy, isDataLoaded, searchResults, type, archivedReportsIdSet, formatPhoneNumber, accountID, queryJSON, email, reportMetadata]);
+    }, [searchKey, exportReportActions, validGroupBy, isDataLoaded, searchResults, type, archivedReportsIdSet, formatPhoneNumber, accountID, queryJSON, email, isActionLoadingSet]);
 
     useEffect(() => {
         /** We only want to display the skeleton for the status filters the first time we load them for a specific data type */
diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
index 656c22282922..318f8764bf19 100644
--- a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
@@ -35,6 +35,7 @@ import {getSections} from '@libs/SearchUIUtils';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
+import {isActionLoadingSetSelector} from '@src/selectors/ReportMetaData';
 import type {ReportAction, ReportActions} from '@src/types/onyx';
 import CardListItemHeader from './CardListItemHeader';
 import MemberListItemHeader from './MemberListItemHeader';
@@ -94,7 +95,7 @@ function TransactionGroupListItem<TItem extends ListItem>({
     const isExpenseReportType = searchType === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT;
     const [transactionsVisibleLimit, setTransactionsVisibleLimit] = useState(CONST.TRANSACTION.RESULTS_PAGE_SIZE as number);
     const [isExpanded, setIsExpanded] = useState(false);
-    const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true});
+    const [isActionLoadingSet = new Set<string>()] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true, selector: isActionLoadingSetSelector});
 
     const transactions = useMemo(() => {
         if (isExpenseReportType) {
@@ -109,13 +110,13 @@ function TransactionGroupListItem<TItem extends ListItem>({
             currentAccountID: accountID,
             currentUserEmail: currentUserDetails.email ?? '',
             formatPhoneNumber,
-            reportMetadata,
+            isActionLoadingSet,
         }) as TransactionListItemType[];
         return sectionData.map((transactionItem) => ({
             ...transactionItem,
             isSelected: selectedTransactionIDsSet.has(transactionItem.transactionID),
         }));
-    }, [isExpenseReportType, transactionsSnapshot?.data, accountID, formatPhoneNumber, groupItem.transactions, selectedTransactionIDsSet, currentUserDetails.email, reportMetadata]);
+    }, [isExpenseReportType, transactionsSnapshot?.data, accountID, formatPhoneNumber, groupItem.transactions, selectedTransactionIDsSet, currentUserDetails.email, isActionLoadingSet]);
 
     const selectedItemsLength = useMemo(() => {
         return transactions.reduce((acc, transaction) => {
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index e3f1baa9a6b8..b0d2c2a08c8b 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -288,7 +288,7 @@ type GetSectionsParams = {
     currentSearch?: SearchKey;
     archivedReportsIDList?: ArchivedReportsIDSet;
     queryJSON?: SearchQueryJSON;
-    reportMetadata?: Record<string, OnyxTypes.ReportMetadata | undefined> | undefined;
+    isActionLoadingSet?: ReadonlySet<string>;
 };
 
 /**
@@ -1388,7 +1388,7 @@ function getReportSections(
     currentAccountID: number | undefined,
     currentUserEmail: string,
     formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
-    reportMetadata: Record<string, OnyxTypes.ReportMetadata | undefined> | undefined,
+    isActionLoadingSet: ReadonlySet<string> | undefined,
     reportActions: Record<string, OnyxTypes.ReportAction[]> = {},
 ): TransactionGroupListItemType[] {
     const shouldShowMerchant = getShouldShowMerchant(data);
@@ -1426,7 +1426,7 @@ function getReportSections(
             const actions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportItem.reportID}`];
 
             let shouldShow = true;
-            if (queryJSON && reportMetadata && !reportMetadata[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportItem.reportID}`]?.isActionLoading) {
+            if (queryJSON && isActionLoadingSet?.has(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportItem.reportID}`)) {
                 if (queryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE) {
                     const status = queryJSON.status;
 
@@ -1643,7 +1643,7 @@ function getSections({
     currentSearch = CONST.SEARCH.SEARCH_KEYS.EXPENSES,
     archivedReportsIDList,
     queryJSON,
-    reportMetadata,
+    isActionLoadingSet,
 }: GetSectionsParams) {
     if (type === CONST.SEARCH.DATA_TYPES.CHAT) {
         return getReportActionsSections(data);
@@ -1653,7 +1653,7 @@ function getSections({
     }
 
     if (type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT) {
-        return getReportSections(data, currentSearch, currentAccountID, currentUserEmail, formatPhoneNumber, reportMetadata, reportActions);
+        return getReportSections(data, currentSearch, currentAccountID, currentUserEmail, formatPhoneNumber, isActionLoadingSet, reportActions);
     }
 
     if (groupBy) {
diff --git a/src/selectors/ReportMetaData.ts b/src/selectors/ReportMetaData.ts
index 5e4f45a05b1a..5d05a2bbb43b 100644
--- a/src/selectors/ReportMetaData.ts
+++ b/src/selectors/ReportMetaData.ts
@@ -1,6 +1,20 @@
-import type {OnyxEntry} from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
 import type {ReportMetadata} from '@src/types/onyx';
 
 const isActionLoadingSelector = (reportMetadata: OnyxEntry<ReportMetadata> | undefined) => reportMetadata?.isActionLoading ?? false;
 
-export default isActionLoadingSelector;
+const isActionLoadingSetSelector = (all: OnyxCollection<ReportMetadata>): ReadonlySet<string> => {
+    const ids = new Set<string>();
+    if (!all) {
+        return ids;
+    }
+
+    for (const [key, value] of Object.entries(all)) {
+        if (value?.isActionLoading) {
+            ids.add(key);
+        }
+    }
+    return ids;
+};
+
+export {isActionLoadingSelector, isActionLoadingSetSelector};

From aa8486bfa14cd9be33a9c977577ed758256bd08e Mon Sep 17 00:00:00 2001
From: DylanDylann <dylandylan9112@gmail.com>
Date: Tue, 11 Nov 2025 16:41:44 +0700
Subject: [PATCH 09/11] update import

---
 .../SelectionListWithSections/Search/ReportListItemHeader.tsx   | 2 +-
 .../SelectionListWithSections/Search/TransactionListItem.tsx    | 2 +-
 .../Search/UserInfoAndActionButtonRow.tsx                       | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx b/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx
index 54fe3aaad446..2307830ec3a9 100644
--- a/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx
+++ b/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx
@@ -16,7 +16,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
 import {handleActionButtonPress} from '@userActions/Search';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import isActionLoadingSelector from '@src/selectors/ReportMetaData';
+import {isActionLoadingSelector} from '@src/selectors/ReportMetaData';
 import type {Policy} from '@src/types/onyx';
 import type {SearchReport} from '@src/types/onyx/SearchResults';
 import ActionCell from './ActionCell';
diff --git a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
index 7a36c7988529..18fb5d8c8f89 100644
--- a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx
@@ -24,7 +24,7 @@ import {isViolationDismissed, shouldShowViolation} from '@libs/TransactionUtils'
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import isActionLoadingSelector from '@src/selectors/ReportMetaData';
+import {isActionLoadingSelector} from '@src/selectors/ReportMetaData';
 import type {Policy, ReportAction, ReportActions} from '@src/types/onyx';
 import type {SearchReport} from '@src/types/onyx/SearchResults';
 import type {TransactionViolation} from '@src/types/onyx/TransactionViolation';
diff --git a/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx b/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
index 0a995fa9e0a9..eba0588ee621 100644
--- a/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
+++ b/src/components/SelectionListWithSections/Search/UserInfoAndActionButtonRow.tsx
@@ -10,7 +10,7 @@ import {isCorrectSearchUserName} from '@libs/SearchUIUtils';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import isActionLoadingSelector from '@src/selectors/ReportMetaData';
+import {isActionLoadingSelector} from '@src/selectors/ReportMetaData';
 import ActionCell from './ActionCell';
 import UserInfoCellsWithArrow from './UserInfoCellsWithArrow';
 

From a3b19cb23ba265f02db729c95b9280c80004e474 Mon Sep 17 00:00:00 2001
From: DylanDylann <dylandylan9112@gmail.com>
Date: Thu, 13 Nov 2025 17:01:16 +0700
Subject: [PATCH 10/11] add removing logic

---
 src/libs/actions/Search.ts | 72 +++++++++++++++++++++++++-------------
 1 file changed, 48 insertions(+), 24 deletions(-)

diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index 7a4d3446640c..8f25766bbf4b 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -453,7 +453,7 @@ function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], com
 // eslint-disable-next-line @typescript-eslint/no-deprecated
 function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], policy: Policy[], transactionIDList?: string[], currentSearchKey?: SearchKey) {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null): OnyxUpdate[] => {
+    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null, shouldRemoveReportFromView: boolean = false): OnyxUpdate[] => {
         const optimisticData: OnyxUpdate[] = [
             {
                 onyxMethod: Onyx.METHOD.MERGE,
@@ -461,7 +461,10 @@ function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], po
                 value: {
                     data: transactionIDList
                         ? (Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>)
-                        : undefined,
+                        : // eslint-disable-next-line @typescript-eslint/no-deprecated
+                          shouldRemoveReportFromView
+                          ? (Object.fromEntries(reportList.map((report) => [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, null])) as Partial<SearchReport>)
+                          : undefined,
                 },
             },
         ];
@@ -481,7 +484,7 @@ function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], po
     const optimisticData: OnyxUpdate[] = createOnyxData({isActionLoading: true});
     const failureData: OnyxUpdate[] = createOnyxData({isActionLoading: false});
     // If we are on the 'Submit' suggested search, remove the report from the view once the action is taken, don't wait for the view to be re-fetched via Search
-    const successData: OnyxUpdate[] = currentSearchKey === CONST.SEARCH.SEARCH_KEYS.SUBMIT ? createOnyxData(null) : createOnyxData({isActionLoading: false});
+    const successData: OnyxUpdate[] = currentSearchKey === CONST.SEARCH.SEARCH_KEYS.SUBMIT ? createOnyxData({isActionLoading: false}, true) : createOnyxData({isActionLoading: false});
 
     // eslint-disable-next-line @typescript-eslint/no-deprecated
     const report = (reportList.at(0) ?? {}) as SearchReport;
@@ -498,7 +501,7 @@ function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], po
 
 function approveMoneyRequestOnSearch(hash: number, reportIDList: string[], transactionIDList?: string[], currentSearchKey?: SearchKey) {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null): OnyxUpdate[] => {
+    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null, shouldRemoveReportFromView: boolean = false): OnyxUpdate[] => {
         const optimisticData: OnyxUpdate[] = [
             {
                 onyxMethod: Onyx.METHOD.MERGE,
@@ -506,7 +509,10 @@ function approveMoneyRequestOnSearch(hash: number, reportIDList: string[], trans
                 value: {
                     data: transactionIDList
                         ? (Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>)
-                        : undefined,
+                        : // eslint-disable-next-line @typescript-eslint/no-deprecated
+                          shouldRemoveReportFromView
+                          ? (Object.fromEntries(reportIDList.map((reportID) => [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, null])) as Partial<SearchReport>)
+                          : undefined,
                 },
             },
         ];
@@ -529,7 +535,7 @@ function approveMoneyRequestOnSearch(hash: number, reportIDList: string[], trans
     // If we are on the 'Approve', `Unapproved cash` or the `Unapproved company cards` suggested search, remove the report from the view once the action is taken, don't wait for the view to be re-fetched via Search
     const approveActionSuggestedSearches: Partial<SearchKey[]> = [CONST.SEARCH.SEARCH_KEYS.APPROVE, CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CASH, CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CARD];
 
-    const successData: OnyxUpdate[] = approveActionSuggestedSearches.includes(currentSearchKey) ? createOnyxData(null) : createOnyxData({isActionLoading: false});
+    const successData: OnyxUpdate[] = approveActionSuggestedSearches.includes(currentSearchKey) ? createOnyxData({isActionLoading: false}, true) : createOnyxData({isActionLoading: false});
 
     playSound(SOUNDS.SUCCESS);
     API.write(WRITE_COMMANDS.APPROVE_MONEY_REQUEST_ON_SEARCH, {hash, reportIDList}, {optimisticData, failureData, successData});
@@ -540,27 +546,42 @@ function exportToIntegrationOnSearch(hash: number, reportID: string, connectionN
     const successAction: OptimisticExportIntegrationAction = {...optimisticAction, pendingAction: null};
     const optimisticReportActionID = optimisticAction.reportActionID;
 
-    const createOnyxData = (update: Partial<ReportMetadata> | null, reportAction?: OptimisticExportIntegrationAction | null): OnyxUpdate[] => [
-        {
-            onyxMethod: Onyx.METHOD.MERGE,
-            key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`,
-            value: {
-                ...update,
+    const createOnyxData = (update: Partial<ReportMetadata> | null, reportAction?: OptimisticExportIntegrationAction | null, shouldRemoveReportFromView: boolean = false): OnyxUpdate[] => {
+        const optimisticData: OnyxUpdate[] = [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`,
+                value: {
+                    ...update,
+                },
             },
-        },
-        {
-            onyxMethod: Onyx.METHOD.MERGE,
-            key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
-            value: {
-                [optimisticReportActionID]: reportAction,
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
+                value: {
+                    [optimisticReportActionID]: reportAction,
+                },
             },
-        },
-    ];
+        ];
+        if (shouldRemoveReportFromView) {
+            optimisticData.push({
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
+                value: {
+                    data: {
+                        [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: null,
+                    },
+                },
+            });
+        }
+        return optimisticData;
+    };
 
     const optimisticData: OnyxUpdate[] = createOnyxData({isActionLoading: true}, optimisticAction);
     const failureData: OnyxUpdate[] = createOnyxData({errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), isActionLoading: false}, null);
     // If we are on the 'Export' suggested search, remove the report from the view once the action is taken, don't wait for the view to be re-fetched via Search
-    const successData: OnyxUpdate[] = currentSearchKey === CONST.SEARCH.SEARCH_KEYS.EXPORT ? createOnyxData(null, successAction) : createOnyxData({isActionLoading: false}, successAction);
+    const successData: OnyxUpdate[] =
+        currentSearchKey === CONST.SEARCH.SEARCH_KEYS.EXPORT ? createOnyxData({isActionLoading: false}, successAction, true) : createOnyxData({isActionLoading: false}, successAction);
 
     const params = {
         reportIDList: reportID,
@@ -576,7 +597,7 @@ function exportToIntegrationOnSearch(hash: number, reportID: string, connectionN
 
 function payMoneyRequestOnSearch(hash: number, paymentData: PaymentData[], transactionIDList?: string[], currentSearchKey?: SearchKey) {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null): OnyxUpdate[] => {
+    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null, shouldRemoveReportFromView: boolean = false): OnyxUpdate[] => {
         const optimisticData: OnyxUpdate[] = [
             {
                 onyxMethod: Onyx.METHOD.MERGE,
@@ -584,7 +605,10 @@ function payMoneyRequestOnSearch(hash: number, paymentData: PaymentData[], trans
                 value: {
                     data: transactionIDList
                         ? (Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>)
-                        : undefined,
+                        : // eslint-disable-next-line @typescript-eslint/no-deprecated
+                          shouldRemoveReportFromView
+                          ? (Object.fromEntries(paymentData.map((item) => [`${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, null])) as Partial<SearchReport>)
+                          : undefined,
                 },
             },
         ];
@@ -605,7 +629,7 @@ function payMoneyRequestOnSearch(hash: number, paymentData: PaymentData[], trans
     const optimisticData: OnyxUpdate[] = createOnyxData({isActionLoading: true});
     const failureData: OnyxUpdate[] = createOnyxData({isActionLoading: false, errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')});
     // If we are on the 'Pay' suggested search, remove the report from the view once the action is taken, don't wait for the view to be re-fetched via Search
-    const successData: OnyxUpdate[] = currentSearchKey === CONST.SEARCH.SEARCH_KEYS.PAY ? createOnyxData(null) : createOnyxData({isActionLoading: false});
+    const successData: OnyxUpdate[] = currentSearchKey === CONST.SEARCH.SEARCH_KEYS.PAY ? createOnyxData({isActionLoading: false}, true) : createOnyxData({isActionLoading: false});
 
     // eslint-disable-next-line rulesdir/no-api-side-effects-method
     API.makeRequestWithSideEffects(

From 0bbac19cddbdcd29b4f7fdf252542bb915587a6e Mon Sep 17 00:00:00 2001
From: DylanDylann <dylandylan9112@gmail.com>
Date: Thu, 13 Nov 2025 17:18:39 +0700
Subject: [PATCH 11/11] type fix

---
 src/libs/actions/Search.ts | 59 ++++++++++++++++++++++++--------------
 1 file changed, 37 insertions(+), 22 deletions(-)

diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index 8f25766bbf4b..bf3bca4629f8 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -453,18 +453,23 @@ function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], com
 // eslint-disable-next-line @typescript-eslint/no-deprecated
 function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], policy: Policy[], transactionIDList?: string[], currentSearchKey?: SearchKey) {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null, shouldRemoveReportFromView: boolean = false): OnyxUpdate[] => {
+    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null, shouldRemoveReportFromView = false): OnyxUpdate[] => {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        let data: Partial<SearchTransaction> | Partial<SearchReport> | undefined;
+
+        if (transactionIDList) {
+            data = Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>;
+        } else if (shouldRemoveReportFromView) {
+            // eslint-disable-next-line @typescript-eslint/no-deprecated
+            data = Object.fromEntries(reportList.map((report) => [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, null])) as Partial<SearchReport>;
+        }
+
         const optimisticData: OnyxUpdate[] = [
             {
                 onyxMethod: Onyx.METHOD.MERGE,
                 key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
                 value: {
-                    data: transactionIDList
-                        ? (Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>)
-                        : // eslint-disable-next-line @typescript-eslint/no-deprecated
-                          shouldRemoveReportFromView
-                          ? (Object.fromEntries(reportList.map((report) => [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, null])) as Partial<SearchReport>)
-                          : undefined,
+                    data,
                 },
             },
         ];
@@ -501,18 +506,23 @@ function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], po
 
 function approveMoneyRequestOnSearch(hash: number, reportIDList: string[], transactionIDList?: string[], currentSearchKey?: SearchKey) {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null, shouldRemoveReportFromView: boolean = false): OnyxUpdate[] => {
+    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null, shouldRemoveReportFromView = false): OnyxUpdate[] => {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        let data: Partial<SearchTransaction> | Partial<SearchReport> | undefined;
+
+        if (transactionIDList) {
+            data = Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>;
+        } else if (shouldRemoveReportFromView) {
+            // eslint-disable-next-line @typescript-eslint/no-deprecated
+            data = Object.fromEntries(reportIDList.map((reportID) => [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, null])) as Partial<SearchReport>;
+        }
+
         const optimisticData: OnyxUpdate[] = [
             {
                 onyxMethod: Onyx.METHOD.MERGE,
                 key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
                 value: {
-                    data: transactionIDList
-                        ? (Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>)
-                        : // eslint-disable-next-line @typescript-eslint/no-deprecated
-                          shouldRemoveReportFromView
-                          ? (Object.fromEntries(reportIDList.map((reportID) => [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, null])) as Partial<SearchReport>)
-                          : undefined,
+                    data,
                 },
             },
         ];
@@ -546,7 +556,7 @@ function exportToIntegrationOnSearch(hash: number, reportID: string, connectionN
     const successAction: OptimisticExportIntegrationAction = {...optimisticAction, pendingAction: null};
     const optimisticReportActionID = optimisticAction.reportActionID;
 
-    const createOnyxData = (update: Partial<ReportMetadata> | null, reportAction?: OptimisticExportIntegrationAction | null, shouldRemoveReportFromView: boolean = false): OnyxUpdate[] => {
+    const createOnyxData = (update: Partial<ReportMetadata> | null, reportAction?: OptimisticExportIntegrationAction | null, shouldRemoveReportFromView = false): OnyxUpdate[] => {
         const optimisticData: OnyxUpdate[] = [
             {
                 onyxMethod: Onyx.METHOD.MERGE,
@@ -597,18 +607,23 @@ function exportToIntegrationOnSearch(hash: number, reportID: string, connectionN
 
 function payMoneyRequestOnSearch(hash: number, paymentData: PaymentData[], transactionIDList?: string[], currentSearchKey?: SearchKey) {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
-    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null, shouldRemoveReportFromView: boolean = false): OnyxUpdate[] => {
+    const createOnyxData = (update: Partial<SearchTransaction> | Partial<ReportMetadata> | null, shouldRemoveReportFromView = false): OnyxUpdate[] => {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        let data: Partial<SearchTransaction> | Partial<SearchReport> | undefined;
+
+        if (transactionIDList) {
+            data = Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>;
+        } else if (shouldRemoveReportFromView) {
+            // eslint-disable-next-line @typescript-eslint/no-deprecated
+            data = Object.fromEntries(paymentData.map((item) => [`${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, null])) as Partial<SearchReport>;
+        }
+
         const optimisticData: OnyxUpdate[] = [
             {
                 onyxMethod: Onyx.METHOD.MERGE,
                 key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
                 value: {
-                    data: transactionIDList
-                        ? (Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, update])) as Partial<SearchTransaction>)
-                        : // eslint-disable-next-line @typescript-eslint/no-deprecated
-                          shouldRemoveReportFromView
-                          ? (Object.fromEntries(paymentData.map((item) => [`${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, null])) as Partial<SearchReport>)
-                          : undefined,
+                    data,
                 },
             },
         ];
