From 01354aea2ed705dc0c7a187863f99ee6d3d45a12 Mon Sep 17 00:00:00 2001
From: bartlomiej obudzinski <bartlomiej.obudzinski@callstack.com>
Date: Tue, 24 Mar 2026 12:56:49 +0100
Subject: [PATCH 1/3] perf: remove deepEqual and phantom props from LHN
 rendering

---
 .../LHNOptionsList/LHNOptionsList.tsx         | 32 +-------
 .../LHNOptionsList/OptionRowLHNData.tsx       | 79 +++----------------
 src/components/LHNOptionsList/types.ts        | 42 +---------
 3 files changed, 16 insertions(+), 137 deletions(-)

diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx
index dafe2c1a26da..39df8dc80a82 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';
@@ -56,8 +55,6 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
     const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
     const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION);
     const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT);
-    const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
-    const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
     const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
     const [onboarding] = useOnyx(ONYXKEYS.NVP_ONBOARDING);
     const [isFullscreenVisible] = useOnyx(ONYXKEYS.FULLSCREEN_VISIBILITY);
@@ -66,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;
@@ -166,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;
@@ -181,10 +177,6 @@ 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);
@@ -200,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)) {
@@ -230,24 +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}
                     hasDraftComment={hasDraftComment}
-                    transactionViolations={transactionViolations}
                     onLayout={onLayoutItem}
                     shouldShowRBRorGBRTooltip={shouldShowRBRorGBRTooltip}
-                    activePolicyID={activePolicyID}
                     onboardingPurpose={introSelected?.choice}
                     onboarding={onboarding}
                     isFullscreenVisible={isFullscreenVisible}
@@ -277,10 +256,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
             optionMode,
             shouldDisableFocusOptions,
             onSelectRow,
-            preferredLocale,
-            transactionViolations,
             onLayoutItem,
-            activePolicyID,
             introSelected?.choice,
             onboarding,
             isReportsSplitNavigatorLast,
@@ -298,13 +274,11 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
             reports,
             reportAttributes,
             reportNameValuePairs,
-            transactionViolations,
             policy,
             personalDetails,
             data.length,
             draftComments,
             optionMode,
-            preferredLocale,
             transactions,
             isOffline,
             isScreenFocused,
@@ -316,13 +290,11 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
             reports,
             reportAttributes,
             reportNameValuePairs,
-            transactionViolations,
             policy,
             personalDetails,
             data.length,
             draftComments,
             optionMode,
-            preferredLocale,
             transactions,
             isOffline,
             isScreenFocused,
diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx
index 4ee59ea96247..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,16 +42,12 @@ function OptionRowLHNData({
     const reportID = propsToForward.reportID;
     const {currentReportID: currentReportIDValue} = useCurrentReportIDState();
     const isReportFocused = isOptionFocused && currentReportIDValue === reportID;
-    const optionItemRef = useRef<OptionData | undefined>(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 {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 card = useGetExpensifyCardFromReportAction({reportAction: lastAction, policyID: fullReport?.policyID});
 
@@ -72,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,
diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts
index 7172ffc25da4..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<typeof CONST.OPTION_MODE>;
@@ -54,12 +42,6 @@ type OptionRowLHNDataProps = {
     /** List of users' personal details */
     personalDetails?: PersonalDetailsList;
 
-    /** The preferred language for the app */
-    preferredLocale?: OnyxEntry<Locale>;
-
-    /** The active policy ID */
-    activePolicyID?: string;
-
     /** The onboarding purpose */
     onboardingPurpose?: OnboardingPurpose;
 
@@ -90,32 +72,12 @@ type OptionRowLHNDataProps = {
     /** The action from the parent report */
     parentReportAction?: OnyxEntry<ReportAction>;
 
-    /** The transaction from the parent report action */
-    transaction: OnyxEntry<Transaction>;
-
-    /** The transaction linked to the report's last action */
-    lastReportActionTransaction?: OnyxEntry<Transaction>;
-
     /** Whether a report contains a draft */
     hasDraftComment: boolean;
 
     /** The reportID of the report */
     reportID: string;
 
-    /** Array of report actions for this report */
-    reportActions: OnyxEntry<ReportActions>;
-
-    /**
-     * 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<ReportActions>;
-
-    /** List of transaction violation */
-    transactionViolations: OnyxCollection<TransactionViolation[]>;
-
     /** Toggle between compact and default view */
     viewMode?: OptionMode;
 

From afb406a12510e41abb4727c3a990dd864fca6dda Mon Sep 17 00:00:00 2001
From: bartlomiej obudzinski <bartlomiej.obudzinski@callstack.com>
Date: Thu, 26 Mar 2026 11:18:10 +0100
Subject: [PATCH 2/3] fix: remove duplicate reportAttributes dependency

---
 src/components/LHNOptionsList/LHNOptionsList.tsx | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx
index 036a5ff9e5eb..c10fb9e875b0 100644
--- a/src/components/LHNOptionsList/LHNOptionsList.tsx
+++ b/src/components/LHNOptionsList/LHNOptionsList.tsx
@@ -290,7 +290,6 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
             reportActions,
             reportAttributes,
             reports,
-            reportAttributes,
             reportNameValuePairs,
             policy,
             personalDetails,

From 5e92ccdb164ee979d182ec2c597888a814033174 Mon Sep 17 00:00:00 2001
From: bartlomiej obudzinski <bartlomiej.obudzinski@callstack.com>
Date: Thu, 26 Mar 2026 11:37:13 +0100
Subject: [PATCH 3/3] fix: remove duplicate reportAttributes props in types

---
 src/components/LHNOptionsList/types.ts | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts
index 6c1262622b34..f2eb64cefe86 100644
--- a/src/components/LHNOptionsList/types.ts
+++ b/src/components/LHNOptionsList/types.ts
@@ -121,12 +121,6 @@ type OptionRowLHNDataProps = {
 
     /** The current user's account ID */
     currentUserAccountID: number;
-
-    /** The report attributes for the report */
-    reportAttributes: OnyxEntry<ReportAttributes>;
-
-    /** The derived report attributes for all reports */
-    reportAttributesDerived?: ReportAttributesDerivedValue['reports'];
 };
 
 type OptionRowLHNProps = {
