From f908ae123604deb9247f3f8ae53fce9644d3138c Mon Sep 17 00:00:00 2001 From: "Yuwen Memon (via MelvinBot)" Date: Mon, 23 Mar 2026 21:13:35 +0000 Subject: [PATCH 1/2] Add clearPageTitle cleanup to useDocumentTitle to fix stale tab title after RHP close When the RHP expense detail screen was closed, the document title persisted because the useDocumentTitle hook had no cleanup function to clear currentPageTitle. This adds a clearPageTitle function that resets the module variable without triggering updateDocumentTitle(), avoiding the race condition that the original cleanup caused. Co-authored-by: Yuwen Memon --- src/hooks/useDocumentTitle.ts | 3 ++- .../updateUnread/index.android.ts | 4 +++- .../UnreadIndicatorUpdater/updateUnread/index.ios.ts | 4 +++- src/libs/UnreadIndicatorUpdater/updateUnread/index.ts | 10 +++++++++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/hooks/useDocumentTitle.ts b/src/hooks/useDocumentTitle.ts index ec40ada435006..87e8991f1d8da 100644 --- a/src/hooks/useDocumentTitle.ts +++ b/src/hooks/useDocumentTitle.ts @@ -1,11 +1,12 @@ import {useFocusEffect} from '@react-navigation/native'; import {useCallback} from 'react'; -import {setPageTitle} from '@libs/UnreadIndicatorUpdater/updateUnread'; +import {clearPageTitle, setPageTitle} from '@libs/UnreadIndicatorUpdater/updateUnread'; function useDocumentTitle(title: string) { useFocusEffect( useCallback(() => { setPageTitle(title); + return () => clearPageTitle(); }, [title]), ); } diff --git a/src/libs/UnreadIndicatorUpdater/updateUnread/index.android.ts b/src/libs/UnreadIndicatorUpdater/updateUnread/index.android.ts index 3831a8b975794..80196ba52f75d 100644 --- a/src/libs/UnreadIndicatorUpdater/updateUnread/index.android.ts +++ b/src/libs/UnreadIndicatorUpdater/updateUnread/index.android.ts @@ -7,5 +7,7 @@ const updateUnread: UpdateUnread = () => {}; // eslint-disable-next-line @typescript-eslint/no-unused-vars function setPageTitle(_title: string) {} +function clearPageTitle() {} + export default updateUnread; -export {setPageTitle}; +export {setPageTitle, clearPageTitle}; diff --git a/src/libs/UnreadIndicatorUpdater/updateUnread/index.ios.ts b/src/libs/UnreadIndicatorUpdater/updateUnread/index.ios.ts index 05aa541c8c6d8..df807f74dc0bb 100644 --- a/src/libs/UnreadIndicatorUpdater/updateUnread/index.ios.ts +++ b/src/libs/UnreadIndicatorUpdater/updateUnread/index.ios.ts @@ -13,5 +13,7 @@ const updateUnread: UpdateUnread = (totalCount) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars function setPageTitle(_title: string) {} +function clearPageTitle() {} + export default updateUnread; -export {setPageTitle}; +export {setPageTitle, clearPageTitle}; diff --git a/src/libs/UnreadIndicatorUpdater/updateUnread/index.ts b/src/libs/UnreadIndicatorUpdater/updateUnread/index.ts index 43218d4d880a2..387a211846e90 100644 --- a/src/libs/UnreadIndicatorUpdater/updateUnread/index.ts +++ b/src/libs/UnreadIndicatorUpdater/updateUnread/index.ts @@ -52,5 +52,13 @@ window.addEventListener('popstate', () => { updateUnread(unreadTotalCount); }); +/** + * Clear the current page title without triggering a document title update. + * Used as cleanup in useDocumentTitle to avoid the race condition from setPageTitle(''). + */ +function clearPageTitle() { + currentPageTitle = ''; +} + export default updateUnread; -export {setPageTitle}; +export {setPageTitle, clearPageTitle}; From 515334b5999e954e40f7551043ec46d8fe09daaf Mon Sep 17 00:00:00 2001 From: "Yuwen Memon (via MelvinBot)" Date: Tue, 24 Mar 2026 17:42:31 +0000 Subject: [PATCH 2/2] Revert clearPageTitle cleanup, add useDocumentTitle to SearchMoneyRequestReportPage The clearPageTitle approach caused a race condition with the debounced UnreadIndicatorUpdater, resulting in title flicker and titles stopping to update across tabs. Instead, keep the no-cleanup approach and add the missing useDocumentTitle call to SearchMoneyRequestReportPage so it properly sets the document title when focused. Co-authored-by: Yuwen Memon --- src/hooks/useDocumentTitle.ts | 3 +-- .../updateUnread/index.android.ts | 4 +--- .../UnreadIndicatorUpdater/updateUnread/index.ios.ts | 4 +--- src/libs/UnreadIndicatorUpdater/updateUnread/index.ts | 10 +--------- src/pages/Search/SearchMoneyRequestReportPage.tsx | 10 +++++++++- 5 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/hooks/useDocumentTitle.ts b/src/hooks/useDocumentTitle.ts index 87e8991f1d8da..ec40ada435006 100644 --- a/src/hooks/useDocumentTitle.ts +++ b/src/hooks/useDocumentTitle.ts @@ -1,12 +1,11 @@ import {useFocusEffect} from '@react-navigation/native'; import {useCallback} from 'react'; -import {clearPageTitle, setPageTitle} from '@libs/UnreadIndicatorUpdater/updateUnread'; +import {setPageTitle} from '@libs/UnreadIndicatorUpdater/updateUnread'; function useDocumentTitle(title: string) { useFocusEffect( useCallback(() => { setPageTitle(title); - return () => clearPageTitle(); }, [title]), ); } diff --git a/src/libs/UnreadIndicatorUpdater/updateUnread/index.android.ts b/src/libs/UnreadIndicatorUpdater/updateUnread/index.android.ts index 80196ba52f75d..3831a8b975794 100644 --- a/src/libs/UnreadIndicatorUpdater/updateUnread/index.android.ts +++ b/src/libs/UnreadIndicatorUpdater/updateUnread/index.android.ts @@ -7,7 +7,5 @@ const updateUnread: UpdateUnread = () => {}; // eslint-disable-next-line @typescript-eslint/no-unused-vars function setPageTitle(_title: string) {} -function clearPageTitle() {} - export default updateUnread; -export {setPageTitle, clearPageTitle}; +export {setPageTitle}; diff --git a/src/libs/UnreadIndicatorUpdater/updateUnread/index.ios.ts b/src/libs/UnreadIndicatorUpdater/updateUnread/index.ios.ts index df807f74dc0bb..05aa541c8c6d8 100644 --- a/src/libs/UnreadIndicatorUpdater/updateUnread/index.ios.ts +++ b/src/libs/UnreadIndicatorUpdater/updateUnread/index.ios.ts @@ -13,7 +13,5 @@ const updateUnread: UpdateUnread = (totalCount) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars function setPageTitle(_title: string) {} -function clearPageTitle() {} - export default updateUnread; -export {setPageTitle, clearPageTitle}; +export {setPageTitle}; diff --git a/src/libs/UnreadIndicatorUpdater/updateUnread/index.ts b/src/libs/UnreadIndicatorUpdater/updateUnread/index.ts index 387a211846e90..43218d4d880a2 100644 --- a/src/libs/UnreadIndicatorUpdater/updateUnread/index.ts +++ b/src/libs/UnreadIndicatorUpdater/updateUnread/index.ts @@ -52,13 +52,5 @@ window.addEventListener('popstate', () => { updateUnread(unreadTotalCount); }); -/** - * Clear the current page title without triggering a document title update. - * Used as cleanup in useDocumentTitle to avoid the race condition from setPageTitle(''). - */ -function clearPageTitle() { - currentPageTitle = ''; -} - export default updateUnread; -export {setPageTitle, clearPageTitle}; +export {setPageTitle}; diff --git a/src/pages/Search/SearchMoneyRequestReportPage.tsx b/src/pages/Search/SearchMoneyRequestReportPage.tsx index 3fdccb5a62344..ed804e4a1534a 100644 --- a/src/pages/Search/SearchMoneyRequestReportPage.tsx +++ b/src/pages/Search/SearchMoneyRequestReportPage.tsx @@ -12,6 +12,7 @@ import useShowSuperWideRHPVersion from '@components/WideRHPContextProvider/useSh import WideRHPOverlayWrapper from '@components/WideRHPOverlayWrapper'; import useActionListContextValue from '@hooks/useActionListContextValue'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useDocumentTitle from '@hooks/useDocumentTitle'; import useIsReportReadyToDisplay from '@hooks/useIsReportReadyToDisplay'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -36,6 +37,7 @@ import { getReportAction, isMoneyRequestAction, } from '@libs/ReportActionsUtils'; +import {getReportName} from '@libs/ReportNameUtils'; import {isMoneyRequestReport, isMoneyRequestReportPendingDeletion, isValidReportIDFromPath} from '@libs/ReportUtils'; import {cancelSpansByPrefix} from '@libs/telemetry/activeSpans'; import {doesDeleteNavigateBackUrlIncludeDuplicatesReview, getParentReportActionDeletionStatus, hasLoadedReportActions, isThreadReportDeleted} from '@libs/TransactionNavigationUtils'; @@ -47,7 +49,8 @@ import {clearDeleteTransactionNavigateBackUrl, createTransactionThreadReport, op import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; -import type {PersonalDetailsList, Policy, Transaction, TransactionViolations} from '@src/types/onyx'; +import {reportByIDsSelector} from '@src/selectors/Attributes'; +import type {PersonalDetailsList, Policy, ReportAttributesDerivedValue, Transaction, TransactionViolations} from '@src/types/onyx'; import {getEmptyObject} from '@src/types/utils/EmptyObject'; type SearchMoneyRequestPageProps = @@ -159,6 +162,11 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) { const oneTransactionID = reportTransactions.at(0)?.transactionID; const reportID = report?.reportID; + + const reportAttributesSelector = useCallback((attributes: OnyxEntry) => reportByIDsSelector(reportID ? [reportID] : [])(attributes), [reportID]); + const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: reportAttributesSelector}); + useDocumentTitle(getReportName(report, reportAttributes)); + const doesReportIDLookValid = isValidReportIDFromPath(reportID); const hasLoadedReportActionsForAccessError = hasLoadedReportActions(reportMetadata, isOffline); const isReportPendingDeletion = isMoneyRequestReportPendingDeletion(report);