Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
336f3e2
Extend useDocumentTitle hook to all remaining pages
MelvinBot Feb 24, 2026
279dfa7
Fix: Sort imports to match Prettier configuration
MelvinBot Feb 24, 2026
6b374c2
Merge remote-tracking branch 'origin/main' into claude-extendUseDocum…
MelvinBot Feb 24, 2026
17c76b1
Fix: Update WalletStatementPage for new addEncryptedAuthTokenToURL si…
MelvinBot Feb 24, 2026
30870d4
Extract useWorkspaceDocumentTitle and useDomainDocumentTitle hooks
MelvinBot Feb 25, 2026
044e3fc
Merge remote-tracking branch 'origin/main' into claude-extendUseDocum…
MelvinBot Feb 25, 2026
82a3f70
Gate useDocumentTitle on screen focus to prevent title overwrites fro…
MelvinBot Feb 25, 2026
13ee018
Merge branch 'claude-extendUseDocumentTitle' of https://github.com/Ex…
MelvinBot Feb 25, 2026
3ea905c
Fix: Sort imports to match Prettier configuration
MelvinBot Feb 25, 2026
897a128
Merge main into claude-extendUseDocumentTitle to resolve conflicts
MelvinBot Feb 25, 2026
3937719
Fix: Reset SearchPage, ReportScreen, and WorkspaceOverviewPage to mai…
MelvinBot Feb 25, 2026
2df5cfa
Remove unrelated changes, keep only document-title feature
MelvinBot Feb 26, 2026
eb7bd34
Fix: Sort imports to match Prettier configuration
MelvinBot Feb 26, 2026
b565d4f
Merge remote-tracking branch 'origin/main' into claude-extendUseDocum…
MelvinBot Feb 26, 2026
c360664
Merge remote-tracking branch 'app/main' into claude-extendUseDocument…
MelvinBot Feb 26, 2026
63eae71
Merge remote-tracking branch 'origin/main' into claude-extendUseDocum…
MelvinBot Mar 2, 2026
8f36331
Re-add useDocumentTitle changes from reverted PR #81150
MelvinBot Mar 2, 2026
8573721
Merge remote-tracking branch 'origin/main' into claude-extendUseDocum…
MelvinBot Mar 4, 2026
6f04f02
Re-apply useDocumentTitle changes after merging main
MelvinBot Mar 4, 2026
379638e
Fix: Add missing useCallback import to BaseSelectionListWithSections
MelvinBot Mar 4, 2026
bc3fbec
Fix: Add setPageTitle no-op to native updateUnread files and remove i…
MelvinBot Mar 4, 2026
eaaae51
Merge remote-tracking branch 'origin/main' into claude-extendUseDocum…
MelvinBot Mar 4, 2026
81b75f0
Fix: Remove unused useCallback import after merge resolution
MelvinBot Mar 4, 2026
8727829
Retrigger CI: test job 5 timed out (no Jest output produced)
MelvinBot Mar 4, 2026
c14a421
Retrigger CI: SessionTest.tsx timeout was flaky (passes locally in 36s)
MelvinBot Mar 4, 2026
5f2ab52
Fix: Mock document title hooks in tests to prevent timer accumulation
MelvinBot Mar 4, 2026
80c4046
Fix: Add eslint-disable for __esModule naming convention in jest mocks
MelvinBot Mar 4, 2026
b2ce36e
Merge main and resolve conflicts in SearchPage, HomePage, PolicyTrave…
MelvinBot Mar 11, 2026
a8153bd
Merge remote-tracking branch 'origin/main' into claude-extendUseDocum…
MelvinBot Mar 11, 2026
f745212
Remove unrelated changes: assets, docs, TextPill, useDeepCompareRef, …
MelvinBot Mar 12, 2026
5d92465
Add useDocumentTitle to settings sub-pages
MelvinBot Mar 12, 2026
f75b568
Fix: Sort imports alphabetically to pass Prettier check
MelvinBot Mar 12, 2026
1d3bbe5
Fix: Remove useFocusEffect cleanup to fix title race condition in spl…
MelvinBot Mar 12, 2026
84954c6
Fix: Use computed report name for document title instead of raw repor…
MelvinBot Mar 12, 2026
a3ec4e6
Fix: Reorder import to satisfy Prettier formatting
MelvinBot Mar 12, 2026
bd3dc35
Address review feedback: scope reportAttributes selector and add titl…
MelvinBot Mar 12, 2026
8f22b5f
Merge main and resolve conflict in DomainMembersPage
MelvinBot Mar 12, 2026
9aadf40
Fix: Memoize useOnyx selector with useCallback in ReportScreen
MelvinBot Mar 12, 2026
688e47c
Fix: Prettier formatting in ReportScreen.tsx
MelvinBot Mar 12, 2026
3aa6a49
Merge branch 'main' of github.com:Expensify/App into claude-extendUse…
yuwenmemon Mar 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions jest/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,23 @@ jest.mock('@components/ActionSheetAwareScrollView/index.android');
jest.mock('@components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext');

jest.mock('@src/components/KeyboardDismissibleFlatList/KeyboardDismissibleFlatListContext');

// Mock document title hooks as no-ops in tests. The web implementation of setPageTitle uses
// setTimeout(fn, 0) which accumulates in the fake timer queue. Combined with lodash debounce
// in triggerUnreadUpdate (also timer-based), this creates excessive timer churn that causes
// heavy integration tests like SessionTest to exceed their timeout.
jest.mock('@src/hooks/useDocumentTitle', () => ({
// eslint-disable-next-line @typescript-eslint/naming-convention
__esModule: true,
default: jest.fn(),
}));
jest.mock('@src/hooks/useWorkspaceDocumentTitle', () => ({
// eslint-disable-next-line @typescript-eslint/naming-convention
__esModule: true,
default: jest.fn(),
}));
jest.mock('@src/hooks/useDomainDocumentTitle', () => ({
// eslint-disable-next-line @typescript-eslint/naming-convention
__esModule: true,
default: jest.fn(),
}));
14 changes: 14 additions & 0 deletions src/hooks/useDocumentTitle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {useFocusEffect} from '@react-navigation/native';
import {useCallback} from 'react';
import {setPageTitle} from '@libs/UnreadIndicatorUpdater/updateUnread';

function useDocumentTitle(title: string) {
useFocusEffect(
useCallback(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider returning a cleanup function that resets the title when the screen loses focus:

useFocusEffect(
    useCallback(() => {
        setPageTitle(title);
        return () => setPageTitle('');
    }, [title]),
);

Without this, if the user navigates to a screen that doesn't call useDocumentTitle (e.g., an RHP modal or a page this PR didn't cover), the previous screen's title persists instead of falling back to the default "New Expensify". What do we think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — added a cleanup function that resets the title to empty when the screen loses focus, so uncovered screens fall back to the default "New Expensify" title:

useFocusEffect(
    useCallback(() => {
        setPageTitle(title);
        return () => setPageTitle('');
    }, [title]),
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yuwenmemon @MelvinBot I think we had the clean up method earlier and we removed it as it was causing issues with the title not being updated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — you're right. PR #81150 originally introduced useDocumentTitle with a useEffect-based cleanup (return () => setPageTitle('') on unmount). That was reverted in #83589 because of #83582: persistent screens (dontDetachScreen: true) never unmount, so the cleanup never fired and the title got stuck as "Settings - Profile".

The current PR uses useFocusEffect instead of useEffect, which addresses the persistent-screen problem since the cleanup now fires on blur rather than unmount. However, #83586 (which was closed in favor of the revert) attempted a similar useFocusEffect approach but with additional guards — specifically, it checked whether another screen had already set a new title and whether the blur was caused by an RHP/modal overlay (where the underlying screen is still visible). Without those guards, the simple return () => setPageTitle('') here could clear the title when an RHP modal opens on top of a page, briefly showing "New Expensify" even though the user is still contextually on that page.

yuwenmemon — given this history, should we add guards similar to what #83586 proposed, or is the simpler approach acceptable here?

setPageTitle(title);
return () => setPageTitle('');
}, [title]),
);
}

export default useDocumentTitle;
14 changes: 14 additions & 0 deletions src/hooks/useDomainDocumentTitle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type {TranslationPaths} from '@src/languages/types';
import useDocumentTitle from './useDocumentTitle';
import useLocalize from './useLocalize';

/**
* Sets the browser document title for domain pages using the format "domainName - PageTitle".
* Falls back to empty string (default title) if domain name is not yet available.
*/
function useDomainDocumentTitle(domainName: string | undefined, titleKey: TranslationPaths) {
const {translate} = useLocalize();
useDocumentTitle(domainName ? `${domainName} - ${translate(titleKey)}` : '');
}

export default useDomainDocumentTitle;
14 changes: 14 additions & 0 deletions src/hooks/useWorkspaceDocumentTitle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type {TranslationPaths} from '@src/languages/types';
import useDocumentTitle from './useDocumentTitle';
import useLocalize from './useLocalize';

/**
* Sets the browser document title for workspace pages using the format "PolicyName - PageTitle".
* Falls back to empty string (default title) if policy name is not yet available.
*/
function useWorkspaceDocumentTitle(policyName: string | undefined, titleKey: TranslationPaths) {
const {translate} = useLocalize();
useDocumentTitle(policyName ? `${policyName} - ${translate(titleKey)}` : '');
}

export default useWorkspaceDocumentTitle;
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@ import type UpdateUnread from './types';
// Android does not yet implement this
const updateUnread: UpdateUnread = () => {};

// No-op on native — document title is a web-only concept
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function setPageTitle(_title: string) {}

export default updateUnread;
export {setPageTitle};
5 changes: 5 additions & 0 deletions src/libs/UnreadIndicatorUpdater/updateUnread/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ const updateUnread: UpdateUnread = (totalCount) => {
Airship.push.iOS.setBadgeNumber(totalCount);
};

// No-op on native — document title is a web-only concept
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function setPageTitle(_title: string) {}

export default updateUnread;
export {setPageTitle};
34 changes: 29 additions & 5 deletions src/libs/UnreadIndicatorUpdater/updateUnread/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,52 @@ import CONFIG from '@src/CONFIG';
import type UpdateUnread from './types';

let unreadTotalCount = 0;
let currentPageTitle = '';

/**
* Set the page title on web
* Set the current page-specific title (called by useDocumentTitle hook)
* @param title - The page-specific title
*/
const updateUnread: UpdateUnread = (totalCount) => {
const hasUnread = totalCount !== 0;
unreadTotalCount = totalCount;
function setPageTitle(title: string) {
currentPageTitle = title;
// Immediately update the document title when page title changes
updateDocumentTitle();
}

/**
* Update the actual document title and favicon
*/
function updateDocumentTitle() {
const hasUnread = unreadTotalCount !== 0;
// This setTimeout is required because due to how react rendering messes with the DOM, the document title can't be modified synchronously, and we must wait until all JS is done
// running before setting the title.
setTimeout(() => {
// There is a Chrome browser bug that causes the title to revert back to the previous when we are navigating back. Setting the title to an empty string
// seems to improve this issue.
document.title = '';
document.title = hasUnread ? `(${totalCount}) ${CONFIG.SITE_TITLE}` : CONFIG.SITE_TITLE;

// Use page-specific title if available, otherwise use the default SITE_TITLE
const baseTitle = currentPageTitle || CONFIG.SITE_TITLE;
document.title = hasUnread ? `(${unreadTotalCount}) ${baseTitle}` : baseTitle;

const favicon = document.getElementById('favicon');
if (favicon instanceof HTMLLinkElement) {
favicon.href = hasUnread ? CONFIG.FAVICON.UNREAD : CONFIG.FAVICON.DEFAULT;
}
}, 0);
}

/**
* Set the page title on web
*/
const updateUnread: UpdateUnread = (totalCount) => {
unreadTotalCount = totalCount;
updateDocumentTitle();
};

window.addEventListener('popstate', () => {
updateUnread(unreadTotalCount);
});

export default updateUnread;
export {setPageTitle};
4 changes: 4 additions & 0 deletions src/pages/Search/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {useSearchActionsContext, useSearchStateContext} from '@components/Search
import type {SearchParams} from '@components/Search/types';
import {usePlaybackActionsContext} from '@components/VideoPlayerContexts/PlaybackContext';
import useConfirmReadyToOpenApp from '@hooks/useConfirmReadyToOpenApp';
import useDocumentTitle from '@hooks/useDocumentTitle';
import useLocalize from '@hooks/useLocalize';
import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
import usePrevious from '@hooks/usePrevious';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
Expand All @@ -22,6 +24,8 @@ import SearchPageWide from './SearchPageWide';
type SearchPageProps = PlatformStackScreenProps<SearchFullscreenNavigatorParamList, typeof SCREENS.SEARCH.ROOT>;

function SearchPage({route}: SearchPageProps) {
const {translate} = useLocalize();
useDocumentTitle(translate('common.search'));
const {shouldUseNarrowLayout} = useResponsiveLayout();
const styles = useThemeStyles();
const {selectedTransactions, lastSearchType, areAllMatchingItemsSelected, currentSearchKey, currentSearchResults, currentSearchQueryJSON} = useSearchStateContext();
Expand Down
2 changes: 2 additions & 0 deletions src/pages/TeachersUnite/SaveTheWorldPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import MenuItemList from '@components/MenuItemList';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import Section from '@components/Section';
import useDocumentTitle from '@hooks/useDocumentTitle';
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
Expand All @@ -23,6 +24,7 @@ function SaveTheWorldPage() {
const {translate} = useLocalize();
const waitForNavigate = useWaitForNavigation();
const {shouldUseNarrowLayout} = useResponsiveLayout();
useDocumentTitle(translate('sidebarScreen.saveTheWorld'));
const theme = useTheme();
const illustrations = useMemoizedLazyIllustrations(['TeachersUnite']);
const saveTheWorldIllustration = useSaveTheWorldSectionIllustration();
Expand Down
5 changes: 4 additions & 1 deletion src/pages/domain/Admins/DomainAdminsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {adminAccountIDsSelector, adminPendingActionSelector, technicalContactSettingsSelector} from '@selectors/Domain';
import {adminAccountIDsSelector, adminPendingActionSelector, domainNameSelector, technicalContactSettingsSelector} from '@selectors/Domain';
import React from 'react';
import {View} from 'react-native';
import Badge from '@components/Badge';
import Button from '@components/Button';
import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDomainDocumentTitle from '@hooks/useDomainDocumentTitle';
import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand All @@ -25,6 +26,8 @@ type DomainAdminsPageProps = PlatformStackScreenProps<DomainSplitNavigatorParamL

function DomainAdminsPage({route}: DomainAdminsPageProps) {
const {domainAccountID} = route.params;
const [domainName] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, {selector: domainNameSelector});
useDomainDocumentTitle(domainName, 'domain.domainAdmins');
const {translate} = useLocalize();
const styles = useThemeStyles();
const theme = useTheme();
Expand Down
2 changes: 2 additions & 0 deletions src/pages/domain/DomainInitialPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import useConfirmReadyToOpenApp from '@hooks/useConfirmReadyToOpenApp';
import useDocumentTitle from '@hooks/useDocumentTitle';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
Expand Down Expand Up @@ -60,6 +61,7 @@ function DomainInitialPage({route}: DomainInitialPageProps) {
const domainAccountID = route.params?.domainAccountID;
const [domain, domainMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`);
const domainName = domain?.email ? Str.extractEmailDomain(domain.email) : undefined;
useDocumentTitle(domainName ?? '');
const [isAdmin, adminMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS}${domainAccountID}`);
const [domainErrors] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN_ERRORS}${domainAccountID}`);

Expand Down
2 changes: 2 additions & 0 deletions src/pages/domain/DomainSamlPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import RenderHTML from '@components/RenderHTML';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import Section from '@components/Section';
import useDomainDocumentTitle from '@hooks/useDomainDocumentTitle';
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand Down Expand Up @@ -46,6 +47,7 @@ function DomainSamlPage({route}: DomainSamlPageProps) {
const isSamlEnabled = !!domainSettings?.samlEnabled;
const isSamlRequired = !!domainSettings?.samlRequired;
const domainName = domain ? Str.extractEmailDomain(domain.email) : undefined;
useDomainDocumentTitle(domainName, 'domain.saml');
const doesDomainExist = !!domain;

const samlFeatures: FeatureListItem[] = useMemo(
Expand Down
5 changes: 4 additions & 1 deletion src/pages/domain/Groups/DomainGroupsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {DomainSecurityGroupWithID} from '@selectors/Domain';
import {groupsSelector} from '@selectors/Domain';
import {domainNameSelector, groupsSelector} from '@selectors/Domain';
import React from 'react';
import {View} from 'react-native';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
Expand All @@ -8,6 +8,7 @@ import SelectionList from '@components/SelectionList';
import TableListItem from '@components/SelectionList/ListItem/TableListItem';
import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader';
import Text from '@components/Text';
import useDomainDocumentTitle from '@hooks/useDomainDocumentTitle';
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand All @@ -25,6 +26,8 @@ type DomainGroupsPageProps = PlatformStackScreenProps<DomainSplitNavigatorParamL

function DomainGroupsPage({route}: DomainGroupsPageProps) {
const {domainAccountID} = route.params;
const [domainName] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, {selector: domainNameSelector});
useDomainDocumentTitle(domainName, 'domain.groups.title');

const styles = useThemeStyles();
const {translate} = useLocalize();
Expand Down
2 changes: 2 additions & 0 deletions src/pages/domain/Members/DomainMembersPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SingleSelectPopup from '@components/Search/FilterDropdowns/SingleSelectPo
import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader';
import Text from '@components/Text';
import useConfirmModal from '@hooks/useConfirmModal';
import useDomainDocumentTitle from '@hooks/useDomainDocumentTitle';
import useDomainGroupFilter from '@hooks/useDomainGroupFilter';
import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -61,6 +62,7 @@ function DomainMembersPage({route}: DomainMembersPageProps) {
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const [domain] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`);
const [domainName] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, {selector: domainNameSelector});
useDomainDocumentTitle(domainName, 'domain.domainMembers');
// We need to use isSmallScreenWidth here because the DecisionModal is opening from RHP and ShouldUseNarrowLayout layout will not work in this place.
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth} = useResponsiveLayout();
Expand Down
2 changes: 2 additions & 0 deletions src/pages/home/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ReceiptScanDropZone from '@components/ReceiptScanDropZone';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import useConfirmReadyToOpenApp from '@hooks/useConfirmReadyToOpenApp';
import useDocumentTitle from '@hooks/useDocumentTitle';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
Expand All @@ -26,6 +27,7 @@ function HomePage() {
const shouldDisplayLHB = !shouldUseNarrowLayout;
const styles = useThemeStyles();
const {translate} = useLocalize();
useDocumentTitle(translate('common.home'));
const [isLoadingApp = true] = useOnyx(ONYXKEYS.IS_LOADING_APP);
const [isLoadingReportData = false] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA);
const isForYouLoading = !!(isLoadingApp || isLoadingReportData);
Expand Down
7 changes: 7 additions & 0 deletions src/pages/inbox/ReportScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import useAppFocusEvent from '@hooks/useAppFocusEvent';
import useArchivedReportsIdSet from '@hooks/useArchivedReportsIdSet';
import {useCurrentReportIDState} from '@hooks/useCurrentReportID';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDocumentTitle from '@hooks/useDocumentTitle';
import useIsAnonymousUser from '@hooks/useIsAnonymousUser';
import useIsReportReadyToDisplay from '@hooks/useIsReportReadyToDisplay';
import useNetwork from '@hooks/useNetwork';
Expand Down Expand Up @@ -62,6 +63,7 @@ import {
isTransactionThread,
isWhisperAction,
} from '@libs/ReportActionsUtils';
import {getReportName} from '@libs/ReportNameUtils';
import {
canEditReportAction,
canUserPerformWriteAction,
Expand Down Expand Up @@ -103,6 +105,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import {reportByIDsSelector} from '@src/selectors/Attributes';
import type * as OnyxTypes from '@src/types/onyx';
import {getEmptyObject, isEmptyObject} from '@src/types/utils/EmptyObject';
import AccountManagerBanner from './AccountManagerBanner';
Expand Down Expand Up @@ -281,6 +284,10 @@ function ReportScreen({route, navigation, isInSidePanel = false}: ReportScreenPr
);
const reportID = report?.reportID;

const reportAttributesSelector = useCallback((attributes: OnyxEntry<OnyxTypes.ReportAttributesDerivedValue>) => reportByIDsSelector(reportID ? [reportID] : [])(attributes), [reportID]);
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: reportAttributesSelector});
useDocumentTitle(getReportName(report, reportAttributes));

const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.chatReportID}`);
const prevReport = usePrevious(report);
const prevUserLeavingStatus = usePrevious(userLeavingStatus);
Expand Down
2 changes: 2 additions & 0 deletions src/pages/settings/AboutPage/AboutPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ScrollView from '@components/ScrollView';
import Section from '@components/Section';
import Text from '@components/Text';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDocumentTitle from '@hooks/useDocumentTitle';
import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand Down Expand Up @@ -60,6 +61,7 @@ function AboutPage() {
const popoverAnchor = useRef<View>(null);
const waitForNavigate = useWaitForNavigation();
const {shouldUseNarrowLayout} = useResponsiveLayout();
useDocumentTitle(translate('initialSettingsPage.about'));
const aboutIllustration = useAboutSectionIllustration();
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
Expand Down
2 changes: 2 additions & 0 deletions src/pages/settings/Preferences/PreferencesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Section from '@components/Section';
import Switch from '@components/Switch';
import Text from '@components/Text';
import {useCurrencyListActions} from '@hooks/useCurrencyList';
import useDocumentTitle from '@hooks/useDocumentTitle';
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand Down Expand Up @@ -47,6 +48,7 @@ function PreferencesPage() {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
useDocumentTitle(translate('common.preferences'));

return (
<ScreenWrapper
Expand Down
2 changes: 2 additions & 0 deletions src/pages/settings/Profile/ProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import Section from '@components/Section';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDocumentTitle from '@hooks/useDocumentTitle';
import {useMemoizedLazyAsset, useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand Down Expand Up @@ -50,6 +51,7 @@ function ProfilePage() {
const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS);
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const route = useRoute<PlatformStackRouteProp<SettingsSplitNavigatorParamList, typeof SCREENS.SETTINGS.PROFILE.ROOT>>();
useDocumentTitle(translate('common.profile'));
const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP);
const getPronouns = (): string => {
const pronounsKey = currentUserPersonalDetails?.pronouns?.replace(CONST.PRONOUNS.PREFIX, '') ?? '';
Expand Down
2 changes: 2 additions & 0 deletions src/pages/settings/Rules/ExpenseRulesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import SelectionListWithModal from '@components/SelectionListWithModal';
import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader';
import Text from '@components/Text';
import useAutoTurnSelectionModeOffWhenHasNoActiveOption from '@hooks/useAutoTurnSelectionModeOffWhenHasNoActiveOption';
import useDocumentTitle from '@hooks/useDocumentTitle';
import useGenericEmptyStateIllustration from '@hooks/useGenericEmptyStateIllustration';
import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -52,6 +53,7 @@ function ExpenseRulesPage() {
const isMobileSelectionModeEnabled = useMobileSelectionMode();
const [expenseRules = getEmptyArray<ExpenseRule>(), expenseRulesResult] = useOnyx(ONYXKEYS.NVP_EXPENSE_RULES);
const {shouldUseNarrowLayout} = useResponsiveLayout();
useDocumentTitle(translate('expenseRulesPage.title'));
const [selectedRules, setSelectedRules] = useState<string[]>([]);
const [deleteConfirmModalVisible, setDeleteConfirmModalVisible] = useState(false);
const styles = useThemeStyles();
Expand Down
Loading
Loading