From db2f291ab7290d181abce73d16864ac03ede9055 Mon Sep 17 00:00:00 2001 From: MelvinBot Date: Thu, 5 Feb 2026 00:05:45 +0000 Subject: [PATCH 1/6] Fix: Add hasOnceLoadedReportActions guard to all readNewestAction call sites The previous fix (PR #74925) only added the hasOnceLoadedReportActions check to one location in ReportActionsList.tsx. This left 7+ other code paths that could call readNewestAction before the report was shared, causing 401 Unauthorized errors from UpdateRNVPLastReadTime. This commit adds the same guard pattern to all remaining call sites: - ReportActionsList.tsx: handleAppVisibilityMarkAsRead, scrollToBottomAndMarkReportAsRead - MoneyRequestReportActionsList.tsx: all 3 effects/callbacks that call readNewestAction - useReportUnreadMessageScrollTracking.ts: onViewableItemsChanged callback - ReportScreen.tsx: task report useEffect The fix ensures the hooks re-run when hasOnceLoadedReportActions changes from false to true, so reports are correctly marked as read once loaded. Co-authored-by: Monil Bhavsar --- .../MoneyRequestReportActionsList.tsx | 24 +++++++++++++++---- src/pages/inbox/ReportScreen.tsx | 6 ++++- src/pages/inbox/report/ReportActionsList.tsx | 15 +++++++++--- .../useReportUnreadMessageScrollTracking.ts | 14 +++++++++-- 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index c21deb495d39..99242b833a41 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -177,6 +177,7 @@ function MoneyRequestReportActionsList({ const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false}); const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${getNonEmptyStringOnyxID(reportID)}`, {canBeMissing: true}); + const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, {canBeMissing: true}); const shouldShowHarvestCreatedAction = isHarvestCreatedExpenseReport(reportNameValuePairs?.origin, reportNameValuePairs?.originalID); const [offlineModalVisible, setOfflineModalVisible] = useState(false); const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); @@ -326,6 +327,12 @@ function MoneyRequestReportActionsList({ if (!isFocused) { return; } + + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (!reportMetadata?.hasOnceLoadedReportActions) { + return; + } + if (isUnread(report, transactionThreadReport, isReportArchived) || (lastAction && isCurrentActionUnread(report, lastAction, visibleReportActions))) { // On desktop, when the notification center is displayed, isVisible will return false. // Currently, there's no programmatic way to dismiss the notification center panel. @@ -341,7 +348,7 @@ function MoneyRequestReportActionsList({ } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [report.lastVisibleActionCreated, transactionThreadReport?.lastVisibleActionCreated, report.reportID, isVisible]); + }, [report.lastVisibleActionCreated, transactionThreadReport?.lastVisibleActionCreated, report.reportID, isVisible, reportMetadata?.hasOnceLoadedReportActions]); useEffect(() => { if (!isVisible || !isFocused) { @@ -351,6 +358,11 @@ function MoneyRequestReportActionsList({ return; } + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (!reportMetadata?.hasOnceLoadedReportActions) { + return; + } + // In case the user read new messages (after being inactive) with other device we should // show marker based on report.lastReadTime const newMessageTimeReference = lastMessageTime.current && report.lastReadTime && lastMessageTime.current > report.lastReadTime ? userActiveSince.current : report.lastReadTime; @@ -373,7 +385,7 @@ function MoneyRequestReportActionsList({ // We will mark the report as read in the above case which marks the LHN report item as read while showing the new message // marker for the chat messages received while the user wasn't focused on the report or on another browser tab for web. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isFocused, isVisible]); + }, [isFocused, isVisible, reportMetadata?.hasOnceLoadedReportActions]); /** * The index of the earliest message that was received while offline @@ -443,6 +455,7 @@ function MoneyRequestReportActionsList({ // We additionally track the top offset to be able to scroll to the new transaction when it's added scrollingVerticalTopOffset.current = contentOffset.y; }, + hasOnceLoadedReportActions: !!reportMetadata?.hasOnceLoadedReportActions, }); useEffect(() => { @@ -629,8 +642,11 @@ function MoneyRequestReportActionsList({ reportScrollManager.scrollToEnd(); readActionSkipped.current = false; - readNewestAction(report.reportID); - }, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID]); + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (reportMetadata?.hasOnceLoadedReportActions) { + readNewestAction(report.reportID); + } + }, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, reportMetadata?.hasOnceLoadedReportActions]); const scrollToNewTransaction = useCallback( (pageY: number) => { diff --git a/src/pages/inbox/ReportScreen.tsx b/src/pages/inbox/ReportScreen.tsx index 6cac880586ec..608f4ecfb24e 100644 --- a/src/pages/inbox/ReportScreen.tsx +++ b/src/pages/inbox/ReportScreen.tsx @@ -940,9 +940,13 @@ function ReportScreen({route, navigation, isInSidePanel = false}: ReportScreenPr if (!!report?.lastReadTime || !isTaskReport(report)) { return; } + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (!reportMetadata?.hasOnceLoadedReportActions) { + return; + } // After creating the task report then navigating to task detail we don't have any report actions and the last read time is empty so We need to update the initial last read time when opening the task report detail. readNewestAction(report?.reportID); - }, [report]); + }, [report, reportMetadata?.hasOnceLoadedReportActions]); // Reset the ref when navigating to a different report useEffect(() => { diff --git a/src/pages/inbox/report/ReportActionsList.tsx b/src/pages/inbox/report/ReportActionsList.tsx index d33222752e84..4ad364ff9a9d 100644 --- a/src/pages/inbox/report/ReportActionsList.tsx +++ b/src/pages/inbox/report/ReportActionsList.tsx @@ -368,6 +368,7 @@ function ReportActionsList({ setShouldScrollToEndAfterLayout(false); } }, + hasOnceLoadedReportActions: !!reportMetadata?.hasOnceLoadedReportActions, }); useEffect(() => { @@ -430,6 +431,11 @@ function ReportActionsList({ return; } + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (!reportMetadata?.hasOnceLoadedReportActions) { + return; + } + if (!isVisible || !isFocused) { if (!lastMessageTime.current) { lastMessageTime.current = lastAction?.created ?? ''; @@ -464,7 +470,7 @@ function ReportActionsList({ // We will mark the report as read in the above case which marks the LHN report item as read while showing the new message // marker for the chat messages received while the user wasn't focused on the report or on another browser tab for web. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isFocused, isVisible]); + }, [isFocused, isVisible, reportMetadata?.hasOnceLoadedReportActions]); const prevHandleReportChangeMarkAsRead = useRef<() => void>(null); const prevHandleAppVisibilityMarkAsRead = useRef<() => void>(null); @@ -631,8 +637,11 @@ function ReportActionsList({ } reportScrollManager.scrollToBottom(); readActionSkipped.current = false; - readNewestAction(report.reportID); - }, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, backTo]); + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (reportMetadata?.hasOnceLoadedReportActions) { + readNewestAction(report.reportID); + } + }, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, backTo, reportMetadata?.hasOnceLoadedReportActions]); /** * Calculates the ideal number of report actions to render in the first render, based on the screen height and on diff --git a/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts b/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts index 12016b2b56d5..6c84e47c084a 100644 --- a/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts +++ b/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts @@ -23,6 +23,9 @@ type Args = { /** Callback to call on every scroll event */ onTrackScrolling: (event: NativeSyntheticEvent) => void; + + /** Whether the report actions have been loaded at least once */ + hasOnceLoadedReportActions: boolean; }; export default function useReportUnreadMessageScrollTracking({ @@ -32,14 +35,16 @@ export default function useReportUnreadMessageScrollTracking({ onTrackScrolling, unreadMarkerReportActionIndex, isInverted, + hasOnceLoadedReportActions, }: Args) { const [isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible] = useState(false); const isFocused = useIsFocused(); - const ref = useRef<{previousViewableItems: ViewToken[]; reportID: string; unreadMarkerReportActionIndex: number; isFocused: boolean}>({ + const ref = useRef<{previousViewableItems: ViewToken[]; reportID: string; unreadMarkerReportActionIndex: number; isFocused: boolean; hasOnceLoadedReportActions: boolean}>({ reportID, unreadMarkerReportActionIndex, previousViewableItems: [], isFocused: true, + hasOnceLoadedReportActions: false, }); // We want to save the updated value on ref to use it in onViewableItemsChanged // because FlatList requires the callback to be stable and we cannot add a dependency on the useCallback. @@ -52,6 +57,10 @@ export default function useReportUnreadMessageScrollTracking({ ref.current.isFocused = isFocused; }, [isFocused]); + useEffect(() => { + ref.current.hasOnceLoadedReportActions = hasOnceLoadedReportActions; + }, [hasOnceLoadedReportActions]); + /** * On every scroll event we want to: * Show/hide the latest message pill when user is scrolling back/forth in the history of messages. @@ -110,7 +119,8 @@ export default function useReportUnreadMessageScrollTracking({ } // if we're scrolled closer than the offset and read action has been skipped then mark message as read - if (unreadActionVisible && readActionSkippedRef.current) { + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (unreadActionVisible && readActionSkippedRef.current && ref.current.hasOnceLoadedReportActions) { // eslint-disable-next-line no-param-reassign readActionSkippedRef.current = false; readNewestAction(ref.current.reportID); From 70d229754b85f40fbd38066e9af97394af2e8d16 Mon Sep 17 00:00:00 2001 From: MelvinBot Date: Thu, 5 Feb 2026 00:14:54 +0000 Subject: [PATCH 2/6] Fix: Add hasOnceLoadedReportActions parameter to test cases The hook was updated to require hasOnceLoadedReportActions but the test file wasn't updated to include this new required parameter in all test cases, causing typecheck failures. --- tests/unit/useReportUnreadMessageScrollTrackingTest.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit/useReportUnreadMessageScrollTrackingTest.ts b/tests/unit/useReportUnreadMessageScrollTrackingTest.ts index 68ce4f0609ae..beda62ec194c 100644 --- a/tests/unit/useReportUnreadMessageScrollTrackingTest.ts +++ b/tests/unit/useReportUnreadMessageScrollTrackingTest.ts @@ -40,6 +40,7 @@ describe('useReportUnreadMessageScrollTracking', () => { unreadMarkerReportActionIndex: -1, isInverted: true, onTrackScrolling: onTrackScrollingMockFn, + hasOnceLoadedReportActions: true, }), ); @@ -69,6 +70,7 @@ describe('useReportUnreadMessageScrollTracking', () => { isInverted: true, unreadMarkerReportActionIndex: -1, onTrackScrolling: onTrackScrollingMockFn, + hasOnceLoadedReportActions: true, }), ); @@ -95,6 +97,7 @@ describe('useReportUnreadMessageScrollTracking', () => { isInverted: true, unreadMarkerReportActionIndex: 1, onTrackScrolling: onTrackScrollingMockFn, + hasOnceLoadedReportActions: true, }), ); @@ -126,6 +129,7 @@ describe('useReportUnreadMessageScrollTracking', () => { unreadMarkerReportActionIndex: -1, isInverted: true, onTrackScrolling: onTrackScrollingMockFn, + hasOnceLoadedReportActions: true, }), ); @@ -151,6 +155,7 @@ describe('useReportUnreadMessageScrollTracking', () => { unreadMarkerReportActionIndex: 1, isInverted: true, onTrackScrolling: onTrackScrollingMockFn, + hasOnceLoadedReportActions: true, }), ); @@ -180,6 +185,7 @@ describe('useReportUnreadMessageScrollTracking', () => { unreadMarkerReportActionIndex: 1, isInverted: true, onTrackScrolling: onTrackScrollingMockFn, + hasOnceLoadedReportActions: true, }), ); From 9ed3608cf906d0e0bc9dc5a6a97577c3cb7e7a73 Mon Sep 17 00:00:00 2001 From: MelvinBot Date: Fri, 20 Feb 2026 10:37:40 +0000 Subject: [PATCH 3/6] Centralize hasOnceLoadedReportActions guard in readNewestAction Move the hasOnceLoadedReportActions check from individual call sites into readNewestAction itself. The function now accepts the flag as a parameter and skips the API call when false. This prevents 401 errors for any future callers without requiring each to add its own guard. Co-authored-by: Monil Bhavsar --- .../MoneyRequestReportActionsList.tsx | 19 +++---------------- src/libs/actions/Report/index.ts | 9 ++++++++- src/pages/inbox/ReportScreen.tsx | 6 +----- src/pages/inbox/report/ReportActionsList.tsx | 19 +++---------------- .../useReportUnreadMessageScrollTracking.ts | 7 +++---- 5 files changed, 18 insertions(+), 42 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index 806ffbdfdab2..8a3299dde3a3 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -338,18 +338,13 @@ function MoneyRequestReportActionsList({ return; } - // Do not try to mark the report as read if the report has not been loaded and shared with the user - if (!reportMetadata?.hasOnceLoadedReportActions) { - return; - } - if (isUnread(report, transactionThreadReport, isReportArchived) || (lastAction && isCurrentActionUnread(report, lastAction, visibleReportActions))) { // On desktop, when the notification center is displayed, isVisible will return false. // Currently, there's no programmatic way to dismiss the notification center panel. // To handle this, we use the 'referrer' parameter to check if the current navigation is triggered from a notification. const isFromNotification = route?.params?.referrer === CONST.REFERRER.NOTIFICATION; if ((isVisible || isFromNotification) && scrollingVerticalBottomOffset.current < CONST.REPORT.ACTIONS.ACTION_VISIBLE_THRESHOLD) { - readNewestAction(report.reportID); + readNewestAction(report.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); if (isFromNotification) { Navigation.setParams({referrer: undefined}); } @@ -368,11 +363,6 @@ function MoneyRequestReportActionsList({ return; } - // Do not try to mark the report as read if the report has not been loaded and shared with the user - if (!reportMetadata?.hasOnceLoadedReportActions) { - return; - } - // In case the user read new messages (after being inactive) with other device we should // show marker based on report.lastReadTime const newMessageTimeReference = lastMessageTime.current && report.lastReadTime && lastMessageTime.current > report.lastReadTime ? userActiveSince.current : report.lastReadTime; @@ -387,7 +377,7 @@ function MoneyRequestReportActionsList({ return; } - readNewestAction(report.reportID); + readNewestAction(report.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); userActiveSince.current = DateUtils.getDBTime(); // This effect logic to `mark as read` will only run when the report focused has new messages and the App visibility @@ -651,10 +641,7 @@ function MoneyRequestReportActionsList({ reportScrollManager.scrollToEnd(); readActionSkipped.current = false; - // Do not try to mark the report as read if the report has not been loaded and shared with the user - if (reportMetadata?.hasOnceLoadedReportActions) { - readNewestAction(report.reportID); - } + readNewestAction(report.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); }, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, reportMetadata?.hasOnceLoadedReportActions]); const scrollToNewTransaction = useCallback( diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index dd0ad4dd74c8..f7c7e41da775 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -1894,12 +1894,19 @@ function expandURLPreview(reportID: string | undefined, reportActionID: string) /** Marks the new report actions as read * @param shouldResetUnreadMarker Indicates whether the unread indicator should be reset. * Currently, the unread indicator needs to be reset only when users mark a report as read. + * @param hasOnceLoadedReportActions Whether the report actions have been loaded at least once. + * If false, the API call will be skipped to avoid 401 errors from reading reports not yet shared with the user. */ -function readNewestAction(reportID: string | undefined, shouldResetUnreadMarker = false) { +function readNewestAction(reportID: string | undefined, shouldResetUnreadMarker = false, hasOnceLoadedReportActions = true) { if (!reportID) { return; } + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (!hasOnceLoadedReportActions) { + return; + } + const lastReadTime = NetworkConnection.getDBTimeWithSkew(); const optimisticData: Array> = [ diff --git a/src/pages/inbox/ReportScreen.tsx b/src/pages/inbox/ReportScreen.tsx index 89cbd7ad2774..d2e383b5b4d1 100644 --- a/src/pages/inbox/ReportScreen.tsx +++ b/src/pages/inbox/ReportScreen.tsx @@ -925,12 +925,8 @@ function ReportScreen({route, navigation, isInSidePanel = false}: ReportScreenPr if (!!report?.lastReadTime || !isTaskReport(report)) { return; } - // Do not try to mark the report as read if the report has not been loaded and shared with the user - if (!reportMetadata?.hasOnceLoadedReportActions) { - return; - } // After creating the task report then navigating to task detail we don't have any report actions and the last read time is empty so We need to update the initial last read time when opening the task report detail. - readNewestAction(report?.reportID); + readNewestAction(report?.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); }, [report, reportMetadata?.hasOnceLoadedReportActions]); // Reset the ref when navigating to a different report diff --git a/src/pages/inbox/report/ReportActionsList.tsx b/src/pages/inbox/report/ReportActionsList.tsx index d754c3797a82..a5fc767bb7b4 100644 --- a/src/pages/inbox/report/ReportActionsList.tsx +++ b/src/pages/inbox/report/ReportActionsList.tsx @@ -405,18 +405,13 @@ function ReportActionsList({ return; } - // Do not try to mark the report as read if the report has not been loaded and shared with the user - if (!reportMetadata?.hasOnceLoadedReportActions) { - return; - } - if (isUnread(report, transactionThreadReport, isReportArchived) || (lastAction && isCurrentActionUnread(report, lastAction, sortedVisibleReportActions))) { // On desktop, when the notification center is displayed, isVisible will return false. // Currently, there's no programmatic way to dismiss the notification center panel. // To handle this, we use the 'referrer' parameter to check if the current navigation is triggered from a notification. const isFromNotification = route?.params?.referrer === CONST.REFERRER.NOTIFICATION; if ((isVisible || isFromNotification) && scrollingVerticalOffset.current < CONST.REPORT.ACTIONS.ACTION_VISIBLE_THRESHOLD) { - readNewestAction(report.reportID); + readNewestAction(report.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); if (isFromNotification) { Navigation.setParams({referrer: undefined}); } @@ -433,11 +428,6 @@ function ReportActionsList({ return; } - // Do not try to mark the report as read if the report has not been loaded and shared with the user - if (!reportMetadata?.hasOnceLoadedReportActions) { - return; - } - if (!isVisible || !isFocused) { if (!lastMessageTime.current) { lastMessageTime.current = lastAction?.created ?? ''; @@ -463,7 +453,7 @@ function ReportActionsList({ return; } - readNewestAction(report.reportID); + readNewestAction(report.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); userActiveSince.current = DateUtils.getDBTime(); return true; @@ -639,10 +629,7 @@ function ReportActionsList({ } reportScrollManager.scrollToBottom(); readActionSkipped.current = false; - // Do not try to mark the report as read if the report has not been loaded and shared with the user - if (reportMetadata?.hasOnceLoadedReportActions) { - readNewestAction(report.reportID); - } + readNewestAction(report.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); }, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, backTo, introSelected, reportMetadata?.hasOnceLoadedReportActions]); /** diff --git a/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts b/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts index 6c84e47c084a..22de20ea250a 100644 --- a/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts +++ b/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts @@ -44,7 +44,7 @@ export default function useReportUnreadMessageScrollTracking({ unreadMarkerReportActionIndex, previousViewableItems: [], isFocused: true, - hasOnceLoadedReportActions: false, + hasOnceLoadedReportActions, }); // We want to save the updated value on ref to use it in onViewableItemsChanged // because FlatList requires the callback to be stable and we cannot add a dependency on the useCallback. @@ -119,11 +119,10 @@ export default function useReportUnreadMessageScrollTracking({ } // if we're scrolled closer than the offset and read action has been skipped then mark message as read - // Do not try to mark the report as read if the report has not been loaded and shared with the user - if (unreadActionVisible && readActionSkippedRef.current && ref.current.hasOnceLoadedReportActions) { + if (unreadActionVisible && readActionSkippedRef.current) { // eslint-disable-next-line no-param-reassign readActionSkippedRef.current = false; - readNewestAction(ref.current.reportID); + readNewestAction(ref.current.reportID, false, ref.current.hasOnceLoadedReportActions); } // FlatList requires a stable onViewableItemsChanged callback for optimal performance. From 603282c19a24f57a910b04b800771c7b94934e85 Mon Sep 17 00:00:00 2001 From: MelvinBot Date: Fri, 20 Feb 2026 11:33:52 +0000 Subject: [PATCH 4/6] Reorder readNewestAction params: make hasOnceLoadedReportActions the second parameter Co-authored-by: Krishna --- .../MoneyRequestReportActionsList.tsx | 6 +++--- src/libs/actions/Report/index.ts | 6 +++--- src/pages/inbox/ReportScreen.tsx | 2 +- src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx | 2 +- src/pages/inbox/report/ReportActionsList.tsx | 6 +++--- .../inbox/report/useReportUnreadMessageScrollTracking.ts | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index 248e96ba085b..319b793bb614 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -378,7 +378,7 @@ function MoneyRequestReportActionsList({ // To handle this, we use the 'referrer' parameter to check if the current navigation is triggered from a notification. const isFromNotification = route?.params?.referrer === CONST.REFERRER.NOTIFICATION; if ((isVisible || isFromNotification) && scrollingVerticalBottomOffset.current < CONST.REPORT.ACTIONS.ACTION_VISIBLE_THRESHOLD) { - readNewestAction(report.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); + readNewestAction(report.reportID, !!reportMetadata?.hasOnceLoadedReportActions); if (isFromNotification) { Navigation.setParams({referrer: undefined}); } @@ -411,7 +411,7 @@ function MoneyRequestReportActionsList({ return; } - readNewestAction(report.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); + readNewestAction(report.reportID, !!reportMetadata?.hasOnceLoadedReportActions); userActiveSince.current = DateUtils.getDBTime(); // This effect logic to `mark as read` will only run when the report focused has new messages and the App visibility @@ -676,7 +676,7 @@ function MoneyRequestReportActionsList({ reportScrollManager.scrollToEnd(); readActionSkipped.current = false; - readNewestAction(report.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); + readNewestAction(report.reportID, !!reportMetadata?.hasOnceLoadedReportActions); }, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, reportMetadata?.hasOnceLoadedReportActions]); const scrollToNewTransaction = useCallback( diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 898a97114923..5f7e6eb895f2 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -1916,12 +1916,12 @@ function expandURLPreview(reportID: string | undefined, reportActionID: string) } /** Marks the new report actions as read - * @param shouldResetUnreadMarker Indicates whether the unread indicator should be reset. - * Currently, the unread indicator needs to be reset only when users mark a report as read. * @param hasOnceLoadedReportActions Whether the report actions have been loaded at least once. * If false, the API call will be skipped to avoid 401 errors from reading reports not yet shared with the user. + * @param shouldResetUnreadMarker Indicates whether the unread indicator should be reset. + * Currently, the unread indicator needs to be reset only when users mark a report as read. */ -function readNewestAction(reportID: string | undefined, shouldResetUnreadMarker = false, hasOnceLoadedReportActions = true) { +function readNewestAction(reportID: string | undefined, hasOnceLoadedReportActions = true, shouldResetUnreadMarker = false) { if (!reportID) { return; } diff --git a/src/pages/inbox/ReportScreen.tsx b/src/pages/inbox/ReportScreen.tsx index 5ffec21c92a5..e29cb1f63fca 100644 --- a/src/pages/inbox/ReportScreen.tsx +++ b/src/pages/inbox/ReportScreen.tsx @@ -927,7 +927,7 @@ function ReportScreen({route, navigation, isInSidePanel = false}: ReportScreenPr return; } // After creating the task report then navigating to task detail we don't have any report actions and the last read time is empty so We need to update the initial last read time when opening the task report detail. - readNewestAction(report?.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); + readNewestAction(report?.reportID, !!reportMetadata?.hasOnceLoadedReportActions); }, [report, reportMetadata?.hasOnceLoadedReportActions]); // Reset the ref when navigating to a different report diff --git a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx index 857c40bf1cd9..32f8ebf05563 100644 --- a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx @@ -501,7 +501,7 @@ const ContextMenuActions: ContextMenuAction[] = [ successIcon: 'Checkmark', shouldShow: ({type, isUnreadChat}) => type === CONST.CONTEXT_MENU_TYPES.REPORT && isUnreadChat, onPress: (closePopover, {reportID}) => { - readNewestAction(reportID, true); + readNewestAction(reportID, true, true); if (closePopover) { hideContextMenu(true, ReportActionComposeFocusManager.focus); } diff --git a/src/pages/inbox/report/ReportActionsList.tsx b/src/pages/inbox/report/ReportActionsList.tsx index 797b0ba4225e..fa62eeac8753 100644 --- a/src/pages/inbox/report/ReportActionsList.tsx +++ b/src/pages/inbox/report/ReportActionsList.tsx @@ -412,7 +412,7 @@ function ReportActionsList({ // To handle this, we use the 'referrer' parameter to check if the current navigation is triggered from a notification. const isFromNotification = route?.params?.referrer === CONST.REFERRER.NOTIFICATION; if ((isVisible || isFromNotification) && scrollOffsetRef.current < CONST.REPORT.ACTIONS.ACTION_VISIBLE_THRESHOLD) { - readNewestAction(report.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); + readNewestAction(report.reportID, !!reportMetadata?.hasOnceLoadedReportActions); if (isFromNotification) { Navigation.setParams({referrer: undefined}); } @@ -454,7 +454,7 @@ function ReportActionsList({ return; } - readNewestAction(report.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); + readNewestAction(report.reportID, !!reportMetadata?.hasOnceLoadedReportActions); userActiveSince.current = DateUtils.getDBTime(); return true; @@ -630,7 +630,7 @@ function ReportActionsList({ } reportScrollManager.scrollToBottom(); readActionSkipped.current = false; - readNewestAction(report.reportID, false, !!reportMetadata?.hasOnceLoadedReportActions); + readNewestAction(report.reportID, !!reportMetadata?.hasOnceLoadedReportActions); }, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, backTo, introSelected, reportMetadata?.hasOnceLoadedReportActions]); /** diff --git a/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts b/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts index 22de20ea250a..5d9948649ba9 100644 --- a/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts +++ b/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts @@ -122,7 +122,7 @@ export default function useReportUnreadMessageScrollTracking({ if (unreadActionVisible && readActionSkippedRef.current) { // eslint-disable-next-line no-param-reassign readActionSkippedRef.current = false; - readNewestAction(ref.current.reportID, false, ref.current.hasOnceLoadedReportActions); + readNewestAction(ref.current.reportID, ref.current.hasOnceLoadedReportActions); } // FlatList requires a stable onViewableItemsChanged callback for optimal performance. From 1cccc118de2be4aa580fc77f91e32a3336da0fe4 Mon Sep 17 00:00:00 2001 From: MelvinBot Date: Fri, 20 Feb 2026 11:45:36 +0000 Subject: [PATCH 5/6] Make hasOnceLoadedReportActions a required parameter in readNewestAction Co-authored-by: Krishna --- src/libs/actions/Report/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 5f7e6eb895f2..0dd596311a83 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -1921,7 +1921,7 @@ function expandURLPreview(reportID: string | undefined, reportActionID: string) * @param shouldResetUnreadMarker Indicates whether the unread indicator should be reset. * Currently, the unread indicator needs to be reset only when users mark a report as read. */ -function readNewestAction(reportID: string | undefined, hasOnceLoadedReportActions = true, shouldResetUnreadMarker = false) { +function readNewestAction(reportID: string | undefined, hasOnceLoadedReportActions: boolean, shouldResetUnreadMarker = false) { if (!reportID) { return; } From 14029546f6c800d5399b170a2b42d08c067b9456 Mon Sep 17 00:00:00 2001 From: MelvinBot Date: Fri, 20 Feb 2026 11:48:02 +0000 Subject: [PATCH 6/6] Fix: Update test files for readNewestAction parameter swap Co-authored-by: Krishna --- tests/actions/ReportTest.ts | 2 +- tests/ui/UnreadIndicatorsTest.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 0592afed8ece..d2dd1e2abc80 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -452,7 +452,7 @@ describe('actions/Report', () => { // When the user visits the report currentTime = DateUtils.getDBTime(); Report.openReport(REPORT_ID, TEST_INTRO_SELECTED); - Report.readNewestAction(REPORT_ID); + Report.readNewestAction(REPORT_ID, true); waitForBatchedUpdates(); return waitForBatchedUpdates(); }) diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index f8c38a31bb85..c4fb2ce87997 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -672,7 +672,7 @@ describe('Unread Indicators', () => { // Given a read report await signInAndGetAppWithUnreadChat(); - readNewestAction(REPORT_ID, true); + readNewestAction(REPORT_ID, true, true); await waitForBatchedUpdates();