From ffedd2aa2f371eafe23c4897429ab4fbf584cd0b Mon Sep 17 00:00:00 2001 From: bartlomiej obudzinski Date: Thu, 11 Jun 2026 10:27:35 +0200 Subject: [PATCH] perf: remove policies live merge from Search getSections --- .../ListItem/ExpenseReportListItem.tsx | 30 +++++++++++++++---- src/components/Search/index.tsx | 3 -- src/libs/SearchUIUtils.ts | 9 ++---- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx b/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx index 9eeb51f6645d..b8bbc055dd8c 100644 --- a/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx +++ b/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx @@ -20,6 +20,7 @@ import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import {useReportPaymentContext} from '@hooks/usePaymentContext'; +import useReportIsArchived from '@hooks/useReportIsArchived'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -30,12 +31,13 @@ import {syncMissingAttendeesViolation} from '@libs/AttendeeUtils'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {isAttendeeTrackingEnabled} from '@libs/PolicyUtils'; import {getNonHeldAndFullAmount, isInvoiceReport, isOpenExpenseReport, isProcessingReport, isReportPendingDelete} from '@libs/ReportUtils'; +import {getSearchReportAvatarProps, hasVisibleViolations} from '@libs/SearchUIUtils'; import {isOnHold, isViolationDismissed, shouldShowViolation, showPendingCardTransactionsBlockModal} 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, Report} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, Report} from '@src/types/onyx'; import ExpenseReportListItemRow from './ExpenseReportListItemRow'; import type {ExpenseReportListItemProps, ExpenseReportListItemType} from './types'; import useLiveRowCapabilities from './useLiveRowCapabilities'; @@ -77,7 +79,7 @@ function ExpenseReportListItem({ const areAllReportTransactionsSelected = transactionsWithoutPendingDelete.length > 0 && transactionsWithoutPendingDelete.every((transaction) => selectedTransactions[transaction.keyForList]?.isSelected); const isSelected = liveRowSelected || areAllReportTransactionsSelected; - const {translate} = useLocalize(); + const {translate, formatPhoneNumber} = useLocalize(); const {isLargeScreenWidth} = useResponsiveLayout(); const {currentSearchHash, currentSearchKey} = useSearchQueryContext(); const {currentSearchResults} = useSearchResultsContext(); @@ -167,8 +169,24 @@ function ExpenseReportListItem({ const {showDelegateNoAccessModal} = useDelegateNoAccessActions(); const {showConfirmModal} = useConfirmModal(); const {showHoldMenu} = useHoldMenuModal(); - const {transactions: reportTransactions} = useTransactionsAndViolationsForReport(reportItem.reportID); + const {transactions: reportTransactions, violations: reportViolations} = useTransactionsAndViolationsForReport(reportItem.reportID); const liveReportTransactions = useMemo(() => Object.values(reportTransactions), [reportTransactions]); + + // Recompute the avatar and violations badge from live policy at the row, replacing the screen-level + // `policies` merge that getSections previously did. Policy comes from the live `policyForViolations` + // (parentPolicy ?? snapshot); violations + transactions come from the report's live Onyx data. + const isReportArchived = useReportIsArchived(reportItem.reportID); + const snapshotPersonalDetails = (searchData?.personalDetailsList ?? {}) as PersonalDetailsList; + const liveAvatarProps = getSearchReportAvatarProps(reportForViolations, formatPhoneNumber, snapshotPersonalDetails, policyForViolations, isReportArchived); + const liveReportItemWithAvatar = {...liveReportItem, ...liveAvatarProps}; + const liveHasVisibleViolations = hasVisibleViolations( + reportForViolations, + reportViolations, + currentUserDetails.email ?? '', + currentUserDetails.accountID, + liveReportTransactions, + policyForViolations, + ); const {currentUserAccountID, currentUserLogin, introSelected, betas, isSelfTourViewed, activePolicy, nextStep, chatReportPolicy, amountOwed} = useReportPaymentContext({ reportID: reportItem.reportID, chatReportPolicyID: parentChatReport?.policyID ?? snapshotChatReport?.policyID, @@ -301,8 +319,8 @@ function ExpenseReportListItem({ // 1. Pre-computed hasVisibleViolations from search data, OR // 2. Synced missingAttendees violation computed at render time (for stale data) // We're using || instead of ?? because the variables are boolean - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const hasAnyVisibleViolations = reportItem?.hasVisibleViolations || hasSyncedMissingAttendeesViolation; + + const hasAnyVisibleViolations = liveHasVisibleViolations || hasSyncedMissingAttendeesViolation; const getDescription = useMemo(() => { if (reportItem?.isRejectedReport) { @@ -386,7 +404,7 @@ function ExpenseReportListItem({ {!isLargeScreenWidth && ( ; currentSearch: SearchKey; currentAccountID: number; currentUserEmail: string; @@ -587,7 +586,6 @@ type GetSectionsResult = [ type GetSectionsParams = { type: SearchDataTypes; data: OnyxTypes.SearchResults['data']; - policies?: OnyxCollection; currentAccountID: number; currentUserEmail: string; translate: LocalizedTranslate; @@ -2735,7 +2733,6 @@ function getReportActionsSections( */ function getReportSections({ data, - policies, currentSearch, currentAccountID, currentUserEmail, @@ -2826,8 +2823,7 @@ function getReportSections({ const formattedTo = !shouldShowBlankTo ? formatPhoneNumber(getDisplayNameOrDefault(toDetails)) : ''; const formattedStatus = getReportStatusTranslation({stateNum: reportItem.stateNum, statusNum: reportItem.statusNum, translate}); - const policyFromKey = getPolicyFromKey(data, reportItem); - const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportItem?.policyID ?? String(CONST.DEFAULT_NUMBER_ID)}`] ?? policyFromKey; + const policy = getPolicyFromKey(data, reportItem); const shouldShowStatusAsPending = !!isOffline && reportItem?.pendingFields?.nextStep === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE; @@ -3573,7 +3569,6 @@ function getListItem(type: SearchDataTypes, status: SearchStatus, groupBy?: Sear function getSections({ type, data, - policies, currentAccountID, currentUserEmail, translate, @@ -3608,7 +3603,6 @@ function getSections({ if (type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT) { return getReportSections({ data, - policies, currentSearch, currentAccountID, currentUserEmail, @@ -6274,6 +6268,7 @@ export { shouldShowDeleteOption, getToFieldValueForTransaction, getSearchReportAvatarProps, + hasVisibleViolations, isTodoSearch, getActiveGroupSearchHashes, getSelectedGroupFilterEntry,