diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index be95526f51a1..c10fb9e875b0 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -24,8 +24,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import getPlatform from '@libs/getPlatform'; import Log from '@libs/Log'; -import {getIOUReportIDOfLastAction} from '@libs/OptionsListUtils'; -import {getLastVisibleActionIncludingTransactionThread, getOriginalMessage, isActionableTrackExpense, isInviteOrRemovedAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; +import {getLastVisibleActionIncludingTransactionThread, getOriginalMessage, isActionableTrackExpense, isInviteOrRemovedAction} from '@libs/ReportActionsUtils'; import {canUserPerformWriteAction as canUserPerformWriteActionUtil} from '@libs/ReportUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -55,8 +54,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const [policy] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); - const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); - const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [onboarding] = useOnyx(ONYXKEYS.NVP_ONBOARDING); const [isFullscreenVisible] = useOnyx(ONYXKEYS.FULLSCREEN_VISIBILITY); @@ -65,7 +63,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const theme = useTheme(); const styles = useThemeStyles(); - const {translate, preferredLocale, localeCompare} = useLocalize(); + const {translate, localeCompare} = useLocalize(); const isReportsSplitNavigatorLast = useRootNavigationState((state) => state?.routes?.at(-1)?.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR); const shouldShowEmptyLHN = data.length === 0; const estimatedItemSize = optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight; @@ -165,7 +163,6 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const itemReportAttributes = reportAttributes?.[reportID]; const itemParentReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${item.parentReportID}`]; const itemReportNameValuePairs = reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`]; - const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; const itemOneTransactionThreadReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${itemReportAttributes?.oneTransactionThreadReportID}`]; const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${item?.parentReportID}`]; const itemParentReportAction = item?.parentReportActionID ? itemParentReportActions?.[item?.parentReportActionID] : undefined; @@ -180,10 +177,10 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const itemInvoiceReceiverPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`]; const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${item?.policyID}`]; - const transactionID = isMoneyRequestAction(itemParentReportAction) - ? (getOriginalMessage(itemParentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID) - : CONST.DEFAULT_NUMBER_ID; - const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const hasDraftComment = + !!draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] && + !draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`]?.match(CONST.REGEX.EMPTY_COMMENT); + const isReportArchived = !!itemReportNameValuePairs?.private_isArchived; const canUserPerformWrite = canUserPerformWriteActionUtil(item, isReportArchived); @@ -195,12 +192,6 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio itemOneTransactionThreadReport?.reportID, ); - const iouReportIDOfLastAction = getIOUReportIDOfLastAction(item, itemReportNameValuePairs?.private_isArchived, visibleReportActionsData, lastAction); - const itemIouReportReportActions = iouReportIDOfLastAction ? reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportIDOfLastAction}`] : undefined; - - const lastReportActionTransactionID = isMoneyRequestAction(lastAction) ? (getOriginalMessage(lastAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID) : CONST.DEFAULT_NUMBER_ID; - const lastReportActionTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${lastReportActionTransactionID}`]; - // Only override lastMessageTextFromReport when a track expense whisper's transaction has been deleted, to prevent showing stale text. let lastMessageTextFromReport: string | undefined; if (isActionableTrackExpense(lastAction)) { @@ -225,23 +216,17 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio reportAttributesDerived={reportAttributes} oneTransactionThreadReport={itemOneTransactionThreadReport} reportNameValuePairs={itemReportNameValuePairs} - reportActions={itemReportActions} parentReportAction={itemParentReportAction} - iouReportReportActions={itemIouReportReportActions} policy={itemPolicy} invoiceReceiverPolicy={itemInvoiceReceiverPolicy} personalDetails={personalDetails ?? {}} - transaction={itemTransaction} - lastReportActionTransaction={lastReportActionTransaction} viewMode={optionMode} isOptionFocused={!shouldDisableFocusOptions} lastMessageTextFromReport={lastMessageTextFromReport} onSelectRow={onSelectRow} - preferredLocale={preferredLocale} - transactionViolations={transactionViolations} + hasDraftComment={hasDraftComment} onLayout={onLayoutItem} shouldShowRBRorGBRTooltip={shouldShowRBRorGBRTooltip} - activePolicyID={activePolicyID} onboardingPurpose={introSelected?.choice} onboarding={onboarding} isFullscreenVisible={isFullscreenVisible} @@ -264,16 +249,14 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio reportActions, policy, transactions, + draftComments, personalDetails, firstReportIDWithGBRorRBR, isFullscreenVisible, optionMode, shouldDisableFocusOptions, onSelectRow, - preferredLocale, - transactionViolations, onLayoutItem, - activePolicyID, introSelected?.choice, onboarding, isReportsSplitNavigatorLast, @@ -288,16 +271,16 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const extraData = useMemo( () => [ reportActions, + reportAttributes, reports, reportAttributes, reportNameValuePairs, - transactionViolations, policy, personalDetails, data.length, optionMode, - preferredLocale, transactions, + draftComments, isOffline, isScreenFocused, isReportsSplitNavigatorLast, @@ -305,16 +288,15 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio ], [ reportActions, - reports, reportAttributes, + reports, reportNameValuePairs, - transactionViolations, policy, personalDetails, data.length, optionMode, - preferredLocale, transactions, + draftComments, isOffline, isScreenFocused, isReportsSplitNavigatorLast, diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 57f19977ab3e..32b241701084 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -1,16 +1,13 @@ -import {deepEqual} from 'fast-equals'; -import React, {useMemo, useRef} from 'react'; +import React, {useMemo} from 'react'; import useReportPreviewSenderID from '@components/ReportActionAvatars/useReportPreviewSenderID'; import {useCurrentReportIDState} from '@hooks/useCurrentReportID'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useGetExpensifyCardFromReportAction from '@hooks/useGetExpensifyCardFromReportAction'; import useOnyx from '@hooks/useOnyx'; -import usePrevious from '@hooks/usePrevious'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import {getMovedReportID} from '@src/libs/ModifiedExpenseMessage'; -import type {OptionData} from '@src/libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Icon} from '@src/types/onyx/OnyxCommon'; import OptionRowLHN from './OptionRowLHN'; @@ -29,16 +26,10 @@ function OptionRowLHNData({ reportAttributesDerived, oneTransactionThreadReport, reportNameValuePairs, - reportActions, personalDetails = {}, - preferredLocale = CONST.LOCALES.DEFAULT, policy, invoiceReceiverPolicy, parentReportAction, - iouReportReportActions, - transaction, - lastReportActionTransaction, - transactionViolations, lastMessageTextFromReport, localeCompare, translate, @@ -51,18 +42,12 @@ function OptionRowLHNData({ const reportID = propsToForward.reportID; const {currentReportID: currentReportIDValue} = useCurrentReportIDState(); const isReportFocused = isOptionFocused && currentReportIDValue === reportID; - const optionItemRef = useRef(undefined); const [movedFromReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getMovedReportID(lastAction, CONST.REPORT.MOVE_TYPE.FROM)}`); const [movedToReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getMovedReportID(lastAction, CONST.REPORT.MOVE_TYPE.TO)}`); const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); - const [draftComment] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`); const {login} = useCurrentUserPersonalDetails(); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fullReport?.policyID}`); - // Check the report errors equality to avoid re-rendering when there are no changes - const prevReportErrors = usePrevious(reportAttributes?.reportErrors); - const areReportErrorsEqual = useMemo(() => deepEqual(prevReportErrors, reportAttributes?.reportErrors), [prevReportErrors, reportAttributes?.reportErrors]); - const hasDraftComment = !!draftComment && !draftComment.match(CONST.REGEX.EMPTY_COMMENT); const card = useGetExpensifyCardFromReportAction({reportAction: lastAction, policyID: fullReport?.policyID}); @@ -74,76 +59,34 @@ function OptionRowLHNData({ chatReport: chatReportForIOU, }); - const optionItem = useMemo(() => { - // Note: ideally we'd have this as a dependent selector in onyx! - const item = SidebarUtils.getOptionData({ - report: fullReport, - reportAttributes, - oneTransactionThreadReport, - reportNameValuePairs, - personalDetails, - policy, - parentReportAction, - conciergeReportID, - lastMessageTextFromReport, - invoiceReceiverPolicy, - card, - lastAction, - translate, - localeCompare, - isReportArchived, - lastActionReport, - movedFromReport, - movedToReport, - currentUserAccountID, - reportAttributesDerived, - policyTags, - currentUserLogin: login ?? '', - }); - if (deepEqual(item, optionItemRef.current)) { - return optionItemRef.current; - } - - optionItemRef.current = item; - - return item; - // Listen parentReportAction to update title of thread report when parentReportAction changed - // Listen to transaction to update title of transaction report when transaction changed - // Listen to lastAction to update when action is deleted or gets pendingAction - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - fullReport, - reportAttributes?.brickRoadStatus, - reportAttributes?.actionBadge, - reportAttributes?.actionTargetReportActionID, - reportAttributes?.reportName, - areReportErrorsEqual, + // React Compiler auto-memoizes each expression in OptionRowLHN independently, + // so there is no need to stabilize the optionItem reference with deepEqual. + // When getOptionData returns a fresh object with the same content, the Compiler + // ensures that only expressions whose inputs actually changed recompute. + const optionItem = SidebarUtils.getOptionData({ + report: fullReport, + reportAttributes, oneTransactionThreadReport, reportNameValuePairs, - lastReportActionTransaction, - reportActions, personalDetails, - preferredLocale, policy, parentReportAction, conciergeReportID, - iouReportReportActions, - transaction, - invoiceReceiverPolicy, lastMessageTextFromReport, + invoiceReceiverPolicy, card, lastAction, - lastActionReport, translate, localeCompare, isReportArchived, + lastActionReport, movedFromReport, movedToReport, currentUserAccountID, reportAttributesDerived, policyTags, - login, - ]); + currentUserLogin: login ?? '', + }); // For single-sender IOUs, trim to the sender's avatar to match the header. // The header uses reportPreviewSenderID as accountID for its primary avatar, @@ -165,7 +108,6 @@ function OptionRowLHNData({ optionItem={finalOptionItem} report={fullReport} conciergeReportID={conciergeReportID} - hasDraftComment={hasDraftComment} /> ); } diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index abdfa9705e91..f2eb64cefe86 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,23 +1,11 @@ import type {RefObject} from 'react'; import type {LayoutChangeEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {LocaleContextProps, LocalizedTranslate} from '@components/LocaleContextProvider'; import type CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; -import type { - Locale, - Onboarding, - OnboardingPurpose, - PersonalDetailsList, - Policy, - Report, - ReportAction, - ReportActions, - ReportNameValuePairs, - Transaction, - TransactionViolation, -} from '@src/types/onyx'; +import type {Onboarding, OnboardingPurpose, PersonalDetailsList, Policy, Report, ReportAction, ReportNameValuePairs} from '@src/types/onyx'; import type {ReportAttributes, ReportAttributesDerivedValue} from '@src/types/onyx/DerivedValues'; type OptionMode = ValueOf; @@ -54,12 +42,6 @@ type OptionRowLHNDataProps = { /** List of users' personal details */ personalDetails?: PersonalDetailsList; - /** The preferred language for the app */ - preferredLocale?: OnyxEntry; - - /** The active policy ID */ - activePolicyID?: string; - /** The onboarding purpose */ onboardingPurpose?: OnboardingPurpose; @@ -90,29 +72,12 @@ type OptionRowLHNDataProps = { /** The action from the parent report */ parentReportAction?: OnyxEntry; - /** The transaction from the parent report action */ - transaction: OnyxEntry; - - /** The transaction linked to the report's last action */ - lastReportActionTransaction?: OnyxEntry; + /** Whether a report contains a draft */ + hasDraftComment: boolean; /** The reportID of the report */ reportID: string; - /** Array of report actions for this report */ - reportActions: OnyxEntry; - - /** - * Array of report actions for the IOU report related to the last action of this report. - * If the last action is a report action preview, the last message of the report depends on - * the report actions of the IOU report linked to the report action preview. - * Changes in the IOU report report actions will affect the last message of this report. - */ - iouReportReportActions: OnyxEntry; - - /** List of transaction violation */ - transactionViolations: OnyxCollection; - /** Toggle between compact and default view */ viewMode?: OptionMode;