Skip to content

[Due for payment 2026-03-31] Tab name does not persist after changing section and back to the previous section #85352

Closed

Description

@nlemma

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Version Number: 9.3.38-0
Reproducible in staging?: Yes
Reproducible in production?: N/A - New feature
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #83388
Email or phone of affected tester (no customers): sdijnsoijsodosj@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Mac 26.2 / Chrome
App Component: Other

Action Performed:

  1. Go to staging.new.expensify.com
  2. Go to Home tab.
  3. Go to Inbox.
    → Tab name shows report name.
  4. Open a different report.
  5. Open the same report in Step 3.
    → Tab name changes to New Expensify.
  6. Go to workspace settings > Overview.
    → Tab name shows workspace name - Overview.
  7. Go to Members, then go back to Overview.
    → Tab name changes to New Expensify.
  8. Go to Account > Profile.
    → Tab name shows Profile.
  9. Go to Subscription and back to Profile.
    → Tab name changes New Expensify.

Expected Result:

In Step 5, 7 and 9, tab will show currently opened page.

Actual Result:

In Step 5, 7 and 9, tab name does not persist and shows New Expensify.

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome / Safari

Screenshots/Videos

Bug7104075_1773656728910.4.mp4

View all open jobs on GitHub

Activity

  1. added
    DeployBlockerCashThis issue or pull request should block deployment
    BugSomething is broken. Auto assigns a BugZero manager.
    on Mar 16, 2026
  2. melvin-bot commented on Mar 16, 2026

    @melvin-bot

    You have been assigned to this deploy blocker because you recently merged this PR: #83388

  3. melvin-bot commented on Mar 16, 2026

    @melvin-bot

    💬 A slack conversation has been started in #expensify-open-source

  4. github-actions commented on Mar 16, 2026

    @github-actions
    Contributor

    👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:

    1. Identify the pull request that introduced this issue and revert it.
    2. Find someone who can quickly fix the issue.
    3. Fix the issue yourself.
  5. OSBotify commented on Mar 16, 2026

    @OSBotify
    Contributor

    🔍 Investigation Summary

    Classification: Frontend bug
    Causing PR: #83388 - "Extend useDocumentTitle hook to all remaining pages" by @MelvinBot (High confidence)
    Related Issues: #83378

    Recommendation: REVERT

    The bug is caused by a race condition in the useDocumentTitle hook implementation. When navigating between sections (e.g., Overview → Members → Overview), the cleanup function clears the title but the focus effect does not reliably restore it when returning to the same page.

    Labels: No label changes needed - keeping DeployBlockerCash as this is a frontend bug in the App repository requiring an App PR fix.

    📋 Detailed Analysis

    Evidence

    PR #83388 introduced a new useDocumentTitle hook and applied it to 27+ pages across the app. The issue reports that tab names don't persist when navigating between sections, which matches exactly when this PR was merged (version 9.3.38-0).

    Verification

    Root Cause

    The bug is in /home/runner/_work/App/App/src/hooks/useDocumentTitle.ts:

    function useDocumentTitle(title: string) {
        useFocusEffect(
            useCallback(() => {
                setPageTitle(title);
                return () => setPageTitle('');  // ← This clears the title on blur
            }, [title]),
        );
    }

    The Problem:

    1. When navigating from "Overview" to "Members", the Overview page loses focus
    2. The cleanup function runs: setPageTitle('') clears the title to empty string
    3. When navigating back to Overview, useFocusEffect should call setPageTitle(title) again
    4. However, there's a race condition - the cleanup runs but the re-focus doesn't reliably restore the title
    5. Result: currentPageTitle remains empty, so the document title falls back to "New Expensify"

    This explains why:

    • Step 5 (opening same report twice): title changes to "New Expensify"
    • Step 7 (Overview → Members → Overview): title changes to "New Expensify"
    • Step 9 (Profile → Subscription → Profile): title changes to "New Expensify"

    The cleanup function return () => setPageTitle('') is too aggressive and creates a timing issue with React Navigation's focus effects.

  6. MelvinBot commented on Mar 16, 2026

    @MelvinBot
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When navigating between sections and then returning to a previously visited page, the browser tab title reverts to "New Expensify" instead of showing the page-specific title. This happens in all three reported scenarios: Inbox reports, workspace settings pages, and account settings pages.

    What is the root cause of that problem?

    The root cause is a race condition between the cleanup function in useDocumentTitle and the asynchronous title-update pipeline.

    In src/hooks/useDocumentTitle.ts:9, the useFocusEffect cleanup calls setPageTitle('') whenever the screen loses focus (blurs):

    useFocusEffect(
        useCallback(() => {
            setPageTitle(title);
            return () => setPageTitle('');  // ← clears title to empty on blur
        }, [title]),
    );

    This clears the module-level currentPageTitle variable to an empty string in src/libs/UnreadIndicatorUpdater/updateUnread/index.ts:8.

    Meanwhile, there are multiple asynchronous callers of updateDocumentTitle() that can fire while currentPageTitle is empty:

    1. Navigation state change listener (src/libs/UnreadIndicatorUpdater/index.ts:106-108) — triggers a debounced (300ms) call to updateDocumentTitle() on every navigation
    2. popstate listener (src/libs/UnreadIndicatorUpdater/updateUnread/index.ts:51-53) — triggers a non-debounced call on browser back/forward
    3. Each setPageTitle call itself queues a setTimeout(fn, 0) inside updateDocumentTitle()

    Inside updateDocumentTitle() (line 33), the fallback logic currentPageTitle || CONFIG.SITE_TITLE means any call while currentPageTitle is empty will set document.title to "New Expensify".

    The race condition manifests because:

    • Blur fires → setPageTitle('') → currentPageTitle = '' → queues setTimeout
    • Focus fires → setPageTitle(title) → currentPageTitle = title → queues setTimeout
    • Debounced unread update fires 300ms later → updateDocumentTitle() → queues yet another setTimeout

    The exact ordering of these setTimeout(fn, 0) callbacks relative to when currentPageTitle gets read depends on React's effect lifecycle, the split navigator's route management, and whether screens are unmounted vs kept alive. In practice, there are timing windows — particularly during React's effect cleanup/setup phase when navigating back in split navigators — where the debounced triggerUnreadUpdate or popstate listener fires while currentPageTitle is still empty from the blur cleanup, overwriting the title to "New Expensify".

    What changes do you think we should make in order to solve the problem?

    Remove the cleanup function from useDocumentTitle — change src/hooks/useDocumentTitle.ts to:

    function useDocumentTitle(title: string) {
        useFocusEffect(
            useCallback(() => {
                setPageTitle(title);
                // No cleanup — let the next focused page overwrite the title
            }, [title]),
        );
    }

    This eliminates the race condition entirely. When navigating between pages, each page simply sets its own title on focus. The previous page's title naturally gets overwritten — there's no need to clear it to an empty string first.

    Since PR #83388 extended useDocumentTitle to all major pages, every page will set its own title on focus. For any transient or intermediate screens that don't call useDocumentTitle, the previous page's title persisting briefly is vastly preferable to showing "New Expensify" incorrectly.

    What alternative solutions did you explore? (Optional)

    1. Cancel/ignore stale setTimeout callbacks — could use a generation counter or AbortController pattern in updateDocumentTitle. This adds complexity and doesn't address the fundamental issue that currentPageTitle is cleared unnecessarily.

    2. Replace useFocusEffect with useEffect — would tie the title to component lifecycle (mount/unmount) instead of focus/blur. However, in React Navigation, screens are often kept mounted in the stack, so useEffect wouldn't re-set the title on refocus.

    3. Set cleanup to the default title instead of empty — change cleanup to return () => setPageTitle(CONFIG.SITE_TITLE). This prevents the fallback logic from being triggered but still has unnecessary title thrashing during transitions.

    The proposed fix (removing cleanup) is the simplest and most robust.


    Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, @MelvinBot <your feedback> to refine this analysis, or explain why you are rejecting Melvin's proposal.

  7. IuliiaHerets commented on Mar 16, 2026

    @IuliiaHerets

    Chrome / v9.3.38-1
    Issue is still reproduced

    bandicam.2026-03-16.14-59-29-446.mp4
  8. yuwenmemon commented on Mar 16, 2026

    @yuwenmemon
    Contributor

    Ah, sorry, this was not from that other PR I got overzealous with the copy-paste.

    This is actually not a blocker and could be fixed in a follow-up PR.

  9. 23 remaining items

  10. melvin-bot commented on Mar 24, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  11. melvin-bot commented on Mar 31, 2026

    @melvin-bot

    Skipping the payment summary for this issue since all the assignees are employees or vendors. If this is incorrect, please manually add the payment summary SO.

  12. thesahindia commented on Aug 6, 2026

    @thesahindia
    Member

    This caused #95208. Details are in #96916 description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.DailyKSv2Engineering

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions