diff --git a/src/hooks/useMarkAsRead.ts b/src/hooks/useMarkAsRead.ts index 75aea8f75804..4408ff3a1b6e 100644 --- a/src/hooks/useMarkAsRead.ts +++ b/src/hooks/useMarkAsRead.ts @@ -16,7 +16,7 @@ import type * as OnyxTypes from '@src/types/onyx'; import type {OnyxEntry} from 'react-native-onyx'; import {useIsFocused, useRoute} from '@react-navigation/native'; -import {useEffect, useRef, useState} from 'react'; +import {useEffect, useEffectEvent, useRef, useState} from 'react'; import {DeviceEventEmitter} from 'react-native'; import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails'; @@ -98,7 +98,7 @@ function useMarkAsRead({reportID, report, transactionThreadReport, sortedVisible const didMarkOnReportChangeRef = useRef(false); - useEffect(() => { + const handleReportChangeMarkAsRead = useEffectEvent(() => { didMarkOnReportChangeRef.current = false; if (reportID !== prevReportID) { return; @@ -121,11 +121,16 @@ function useMarkAsRead({reportID, report, transactionThreadReport, sortedVisible } readActionSkippedRef.current = true; - // This effect should only run when the newest visible action changes, otherwise every action/report object update can prematurely consume unread state. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [report?.lastVisibleActionCreated, transactionThreadReport?.lastVisibleActionCreated, reportID, isVisible, isReportActionsLoaded]); + }); + // Only re-run on newest-action changes; otherwise any report update can prematurely consume unread state. useEffect(() => { + handleReportChangeMarkAsRead(); + }, [report?.lastVisibleActionCreated, transactionThreadReport?.lastVisibleActionCreated, reportID, isVisible, isReportActionsLoaded]); + + // isFocused is passed as an arg because the Effect Event closure can be stale (stuck true) on frozen screens, + // re-marking a just-unread report as read on report switch + const handleAppVisibilityMarkAsRead = useEffectEvent((isFocusedArg: boolean) => { if (didMarkOnReportChangeRef.current) { didMarkOnReportChangeRef.current = false; return; @@ -134,7 +139,7 @@ function useMarkAsRead({reportID, report, transactionThreadReport, sortedVisible return; } - if (!isVisible || !Visibility.hasFocus() || !isFocused) { + if (!isVisible || !Visibility.hasFocus() || !isFocusedArg) { if (!lastMessageTime.current) { lastMessageTime.current = lastAction?.created ?? ''; } @@ -159,8 +164,11 @@ function useMarkAsRead({reportID, report, transactionThreadReport, sortedVisible readNewestAction(reportID, true); userActiveSince.current = DateUtils.getDBTime(); - // This effect should only run when app visibility/focus changes; the helper reads the latest report/action values without making every action update mark the report as read. - // eslint-disable-next-line react-hooks/exhaustive-deps + }); + + // Only re-run when app visibility/focus changes, so action updates don't keep marking the report as read. + useEffect(() => { + handleAppVisibilityMarkAsRead(isFocused); }, [isVisible, isFocused]); const markNewestActionAsRead = () => {