Skip to content

Forward/Back navigation buttons disappear after submitting a report#94928

Merged
mountiny merged 7 commits into
Expensify:mainfrom
lorretheboy:fix/93750
Jul 7, 2026
Merged

Forward/Back navigation buttons disappear after submitting a report#94928
mountiny merged 7 commits into
Expensify:mainfrom
lorretheboy:fix/93750

Conversation

@lorretheboy

@lorretheboy lorretheboy commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

I moved the cache out of the swapping children and into the stable parent. MoneyRequestReportNavigation itself never unmounts once shouldKeepMounted is set, so the lastValidReports/effectiveAllReports logic that currently lives in MoneyRequestReportNavigationContent should be lifted there:

const [lastValidReports, setLastValidReports] = useState<Array<string | undefined> | null>(null);
if (liveCurrentIndex !== -1 && !isSameReportList(allReports, lastValidReports)) {
setLastValidReports(allReports);
}
const effectiveAllReports = liveCurrentIndex === -1 && lastValidReports ? lastValidReports : allReports;
const currentIndex = effectiveAllReports.indexOf(reportID);

Compute the allReports source (fast-path context list vs. useSearchSections) in the parent, keep the "last list containing the report" cache there, and pass the resolved effectiveAllReports down to a single presentational content component. That way toggling isSearchLoading only changes which data feeds the cache - it no longer destroys it - so the cached list (still containing the just-submitted report) keeps the arrows visible.

Fixed Issues

$ #93750
PROPOSAL: #93750 (comment)

Tests

  1. Navigate to a workspace chat.
  2. Create 3 reports containing manual expenses.
  3. Navigate to Spend > Drafts.
  4. Open one of the reports from the list.
  5. Observe the Forward/Back navigation buttons in the top navigation area.
  6. Click Submit.
  7. Observe the top navigation area again.
  8. Verify that The Forward/Back navigation buttons should remain visible after submitting the report, allowing the user to navigate to the previous or next report in the filtered list.
  • Verify that no errors appear in the JS console

Offline tests

QA Steps

  1. Navigate to a workspace chat.
  2. Create 3 reports containing manual expenses.
  3. Navigate to Spend > Drafts.
  4. Open one of the reports from the list.
  5. Observe the Forward/Back navigation buttons in the top navigation area.
  6. Click Submit.
  7. Observe the top navigation area again.
  8. Verify that The Forward/Back navigation buttons should remain visible after submitting the report, allowing the user to navigate to the previous or next report in the filtered list.
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
aw.mov
Android: mWeb Chrome
a.mov
iOS: Native
ia.mov
iOS: mWeb Safari
i.mov
MacOS: Chrome / Safari
w.mov

…f allReports and improve component stability
@lorretheboy

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f41e89c72

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx Outdated
@lorretheboy lorretheboy marked this pull request as ready for review June 30, 2026 04:26
@lorretheboy lorretheboy requested review from a team as code owners June 30, 2026 04:26
@melvin-bot melvin-bot Bot requested review from flaviadefaria and mkhutornyi and removed request for a team June 30, 2026 04:26
@melvin-bot

melvin-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

@mkhutornyi Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
...RequestReportView/MoneyRequestReportNavigation.tsx 6.31% <9.52%> (+0.99%) ⬆️
... and 12 files with indirect coverage changes

@mkhutornyi

mkhutornyi commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
Android: mWeb Chrome
iOS: HybridApp
ios.mov
iOS: mWeb Safari
MacOS: Chrome / Safari
web.mov

@mkhutornyi

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd53844ef9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx Outdated

@mkhutornyi mkhutornyi left a comment

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.

Looks good but please check above comment and fix conflict

@MelvinBot

Copy link
Copy Markdown
Contributor

Review

The root-cause analysis is correct and the fix is sound. The bug was a component-subtree swap: the old parent rendered either MoneyRequestReportNavigationContent (fast path) or MoneyRequestReportNavigationStandalone depending on isSearchLoading. Submitting a report toggles isSearchLoading, which swapped one subtree for the other, unmounting the component that owned the lastValidReports useState cache — so the cached list that kept the arrows visible was wiped. Collapsing both paths into a single, always-mounted MoneyRequestReportNavigationContent that selects the source list as a value fixes it correctly. CI is green (the one failing checklist is the author checklist, unrelated).

Two things worth addressing before merge:

1. Performance regression: useSearchSections now runs unconditionally

The entire point of the old fast path was to avoid useSearchSections when the pre-computed sortedReportIDs were already available — the parent got lastSearchQuery and isSearchLoading from two cheap useOnyx selectors and never ran the heavy sections build.

Now MoneyRequestReportNavigationContent calls useSearchSections() on every render while the carousel is mounted, even in the fast-path case where standaloneReports is discarded. That hook subscribes to a dozen Onyx keys (SNAPSHOT, card lists, REPORT_NAME_VALUE_PAIRS, bank accounts, report attributes…) and runs getSections + getSortedSections each render — see useSearchSections.ts. So the fast path no longer skips that work; it just throws the result away.

The comment on the hook call frames this as a "value swap," which is accurate for unmount avoidance but glosses over the fact that the expensive computation now always executes. Could you confirm this is an acceptable trade-off (the perf tests passed, but they may not exercise this carousel)? If the fast-path cost matters, an alternative is to lift the lastValidReports cache up to the stable parent MoneyRequestReportNavigation (which does not remount on the isSearchLoading toggle) and keep deferring useSearchSections.

2. The unit tests re-implement the logic instead of testing the component

useNavigationSource is a hand-written copy of the component's source-selection + cache logic, including a duplicated isSameReportList. The tests exercise that copy, not the real MoneyRequestReportNavigationContent. This means the regression these tests are meant to guard against — the cache being wiped by a remount — can't actually be caught, because the test never renders the component that does the mounting/unmounting. If the component's logic drifts from this mirror, the tests stay green. Rendering the real component (mocking the Onyx/context hooks) and asserting the arrows persist across the submit-triggered isSearchLoading toggle would give the test real teeth.

Minor
  • EMPTY_GUARD and the selectQueryHash/hash reformatting are pure noise diff — harmless but they slightly inflate the review surface.
  • isSameReportList is now duplicated between the component and the test file; if you keep the mirror-style test, consider exporting the real one.

Overall: correct fix for the reported bug. My main ask is a sanity check on (1) — everything else is polish.

@lorretheboy

Copy link
Copy Markdown
Contributor Author

@mkhutornyi I fixed conflicts & resolved codex comments. Kindly check again

@mkhutornyi

Copy link
Copy Markdown
Contributor

@codex review

@MelvinBot

Copy link
Copy Markdown
Contributor

🤖 Regression scan — the fix itself is sound (one stable MoneyRequestReportNavigationContent instance means the lastValidReports cache survives the isSearchLoading toggle, so the arrows no longer disappear after Submit). I found one likely performance regression introduced by that restructuring.

⚠️ Fast path now mounts all of useSearchSections' heavy Onyx subscriptions

On main, the fast path (context has IDs + not loading) rendered MoneyRequestReportNavigationContent without calling useSearchSections — that hook (and its ~8 Onyx subscriptions) only mounted in the Standalone/full path. This PR removes Standalone and makes Content always call useSearchSections(contextReports).

The shouldUseContextReports guard only skips the CPU‑heavy getSections/getSortedSections rebuild — it does not skip the subscriptions themselves (hooks can't be called conditionally). So on the common fast path, this component now subscribes to and re-renders on changes to the full search snapshot, the whole REPORT_NAME_VALUE_PAIRS collection, useReportAttributes (derived), card lists, and bank accounts — none of which it subscribed to before:

useSearchSections.ts:36-48

Net effect: the "fast path" is no longer light. Since this component lives in the report header and shouldKeepMounted keeps it mounted for the whole carousel lifetime, those subscriptions and their re-renders persist the entire time an expense report is open from search. The PR comments still describe the fast path as skipping expensive work, so this looks unintentional.

Suggested mitigation: keep the always-mounted approach (it's required for the fix), but reduce its cost on the fast path — e.g. pass selectors to the useOnyx calls so a full-snapshot change doesn't re-render, or short-circuit the subscription-derived data when shouldUseContextReports is true. At minimum, worth confirming with a High Traffic account that opening a report from search hasn't gotten heavier.

Other things I checked (no regression found)
  • Signature change useSearchSections()useSearchSections(contextReports = []): backward compatible via default param, and MoneyRequestReportNavigation is the only caller — safe.
  • isSearchLoading source: still derived from the snapshot keyed by lastSearchQuery.queryJSON.hash, same value as before (now without a selector — part of the extra re-render surface above).
  • No double filtering: contextReports is filtered once in the parent and returned as-is on the fast path; results is [] on the fast path so useFilterPendingDeleteReports(results) is a no-op.
  • lastValidReports / shouldKeepMounted setState-during-render patterns are pre-existing and content-compared, so no new infinite-loop risk.
  • Tests: the rewritten useNavigationSource mirror re-implements the fast/full selection that now lives inside useSearchSections, so it doesn't actually exercise the hook's new branch — the cache-survives-toggle case is covered, the "subscriptions always mounted" behavior is not.

This is a code-read review (no profiling), so treat the perf impact as a flag to verify rather than a confirmed number. Reply @MelvinBot <feedback> to dig deeper into any of these.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

const currentUserEmail = currentUserDetails.email ?? '';

P2 Badge Avoid mounting heavy subscriptions on the fast path

When contextReports.length > 0 && !isSearchLoading, this guard only skips getSections; the hook has already run the unconditional useOnyx subscriptions above for card feeds, report NVPs, bank accounts, and useReportAttributes. Since MoneyRequestReportNavigation now always calls this hook, opening a report from Search with precomputed sorted IDs no longer uses the previous lightweight fast path and will re-render on those large collections even though it ultimately returns contextReports. Please keep the heavy fallback in a separate component/hook or otherwise avoid mounting these subscriptions for the context path.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@mkhutornyi

Copy link
Copy Markdown
Contributor

@lorretheboy please check ^

@mkhutornyi

Copy link
Copy Markdown
Contributor

@TMisiukiewicz you wanna do secondary review?

Comment on lines -211 to -224
// All Onyx subscriptions via useSearchSections. Mounts if there are no sorted report IDs in the context.
function MoneyRequestReportNavigationStandalone({reportID, shouldDisplayNarrowVersion}: MoneyRequestReportNavigationProps) {
const {allReports, isSearchLoading, lastSearchQuery} = useSearchSections();

return (
<MoneyRequestReportNavigationContent
reportID={reportID}
shouldDisplayNarrowVersion={shouldDisplayNarrowVersion}
allReports={allReports}
isSearchLoading={isSearchLoading}
lastSearchQuery={lastSearchQuery}
/>
);
}

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.

@MelvinBot #86238 was partially reverted. Example: this code was removed.
Won't it cause performance regression back (what #86238 fixed)?

@MelvinBot

Copy link
Copy Markdown
Contributor

Good question — and the distinction matters, so let me be precise.

Short answer: No, this does not bring back the specific regression Expensify/App#86238 fixed. That PR's win was eliminating the per‑render getSections/getSortedSections sort (the 62.8ms→36.7ms @ 250 reports, 1068ms→32ms @ 10k reports numbers). In this PR that sort is still gated and still skipped on the fast path — see useSearchSections.ts:58: if (!shouldUseContextReports && ...). When the precomputed context IDs are usable, shouldUseContextReports is true, the block is bypassed, and allReports is just contextReports. So the O(n·log n)-per-render sort that #86238 measured is not reintroduced.

What the removed fast-path branch did cost: the old code skipped useSearchSections entirely on the fast path. Now MoneyRequestReportNavigationContent always calls it, so the ~10 Onyx subscriptions inside it (useSearchSections.ts:28-48 — full SNAPSHOT, REPORT_NAME_VALUE_PAIRS, card lists, useReportAttributes…) now mount and drive re-renders even on the fast path. That's the regression I flagged in my earlier scan — but it's subscription/re-render overhead, not the sort, so it's a fraction of what #86238 was about, not a full revert of it.

So to your worry directly: the expensive part #86238 removed stays removed; what came back is the lighter subscription cost the fast path used to avoid.

If you want to close even that gap without giving up the always-mounted structure the bug fix needs, the cleanest option is the one from my earlier comment / codex's: lift the lastValidReports cache up to the stable parent MoneyRequestReportNavigation (which doesn't remount on the isSearchLoading toggle) and keep deferring useSearchSections to the standalone path — that preserves both #86238's fast path and this PR's cache-survival fix. Failing that, adding selectors to those useOnyx calls would cut the re-render surface.

Why the sort frequency is unchanged even during Submit

Submitting toggles isSearchLoadingtrue briefly, which flips shouldUseContextReports to false and runs the sort for those render(s), then returns to the fast path. But the old code did the same thing — during loading it rendered MoneyRequestReportNavigationStandalone, which also ran useSearchSections/getSections. So the sort runs on the same occasions as before; only the subscription mounting changed.

@melvin-bot melvin-bot Bot requested a review from mountiny July 6, 2026 15:20
@mountiny mountiny requested a review from TMisiukiewicz July 6, 2026 18:24
@lorretheboy

Copy link
Copy Markdown
Contributor Author

@mkhutornyi I updated code to address this perf issue #94928 (review)

@MelvinBot

Copy link
Copy Markdown
Contributor

🤖 Regression scan — PR #94928

The change is well-scoped and the core fix (keeping one stable MoneyRequestReportNavigationContent mounted so the lastValidReports cache survives the isSearchLoading toggle) is sound. I found no hard functional regression, but three risks worth confirming before merge — one of them is a real test-coverage regression.

1. (Highest priority) The unit test no longer tests the component it's protecting. The updated test defines a local useNavigationSource hook that re-implements (the doc comment even says "Mirrors") the source-selection + cache logic, and asserts against that copy rather than rendering the real MoneyRequestReportNavigationContent. So the "cache survives the isSearchLoading toggle" test — the exact behavior this PR fixes — can stay green even if the real component regresses, because the two can drift. Consider rendering the actual component instead of a mirror.

2. New one-render lag on the slow path. Previously the slow path read useSearchSections().allReports synchronously. Now that list is computed in the child and lifted up via useEffectsetStandaloneReports, so MoneyRequestReportNavigationContent sees a newly-paginated/refreshed list one render later. The lastValidReports cache and the counter (allReportsCount comes from lastSearchQuery.previousLengthOfResults, not list length) bridge this, so it should be imperceptible — but worth an explicit check that a freshly-loaded next page becomes navigable without a visible stutter.

3. Stale standaloneReports during a fast→slow transition. When submitting a report flips isSearchLoading true, allReports falls back to standaloneReports which, until the just-mounted standalone child re-lifts, still holds its previous value ([] if we came straight from the fast path). If that stale value happens to contain reportID, the set-during-render cache write could cache a wrong list for one render. Transient and bridged by the cache, but worth verifying the "Needs Approval" / "Ready to Pay" flows from the issue (not just Drafts) don't briefly show a wrong "X of Y".

What I checked and why the rest looks safe
  • No infinite loop. Both the set-during-render setLastValidReports and the child's onReportsChange updater guard by content (isSameReportList), returning the previous reference when unchanged so React bails out. The child's effect refires each render (new array identity from filter/map) but produces no state change.
  • Fast-path perf restored. On the fast path the standalone child is not rendered (!shouldUseContextReports && …), so the heavy useSearchSections subscriptions (card feeds, report NVPs, bank accounts, report attributes) never mount. The always-mounted content component only subscribes to the light REPORT_NAVIGATION_LAST_SEARCH_QUERY and a boolean loading selector.
  • lastSearchQuery / isSearchLoading equivalence. Reading them directly via useOnyx in the content component is equivalent to what useSearchSections returned (same key REPORT_NAVIGATION_LAST_SEARCH_QUERY, same !!snapshot?.search?.isLoading), so moving them out of the standalone doesn't change values.
  • No dangling references / unused imports after the refactor; both useEffect and useState are used.

I was not able to run the jest suite or a full typecheck in this environment (sandbox blocked npx), so items 2 and 3 are reasoned from the code, not observed at runtime — a quick manual pass through the issue's Needs Approval / Ready to Pay flows would confirm them.


Next Steps: Reply with @MelvinBot <feedback> to refine this scan.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 5db0738836

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@mountiny

mountiny commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@lorretheboy can you update the PR body with an explanation of your changes, please?

@mountiny mountiny merged commit 4a76d16 into Expensify:main Jul 7, 2026
36 checks passed
@OSBotify

OSBotify commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🚧 mountiny has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
Built from App PR #94928.

Android 🤖 iOS 🍎
https://ad-hoc-expensify-cash.s3.us-east-1.amazonaws.com/rock-artifacts/ad-hoc/rock-android-Adhoc-4a76d16ced8-c9ce36c-4ad48d017eb740b24a2e055becb79e41f5cf4593/index.html ❌ FAILED ❌
Android The QR code can't be generated, because the iOS build failed
Web 🕸️
https://94928.pr-testing.expensify.com
Web

👀 View the workflow run that generated this build 👀

@lorretheboy

Copy link
Copy Markdown
Contributor Author

@mountiny I updated the summary of changes

@OSBotify

OSBotify commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/mountiny in version: 9.4.32-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@MelvinBot

Copy link
Copy Markdown
Contributor

Help site review — no changes required

I reviewed the changes in this PR and no updates to the help site (docs/articles) are needed, so I did not create a draft docs PR.

Why:

  • This is a bug fix / internal refactor, not a feature or behavior change. It moves the lastValidReports / effectiveAllReports cache from MoneyRequestReportNavigationContent up into the stable parent MoneyRequestReportNavigation so the Forward/Back arrows stop disappearing after a report is submitted.
  • It restores the already-expected behavior (arrows remain visible after submit) rather than introducing anything new.
  • There are no new or renamed features, tabs, settings labels, or buttons — the only two files touched are the component and its unit test:
    • src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
    • tests/unit/components/MoneyRequestReportNavigation.test.tsx
  • I searched docs/articles for content documenting the report Forward/Back navigation arrows and found none — the help site does not describe this navigation control, so there is nothing to update to match this fix.

Since there is nothing user-facing to document, there is no linked help site PR to open, label, or assign.

lorretheboy — if you believe a specific help article should nonetheless be added or updated for this flow, let me know which article/behavior and I'll draft it.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/grgia in version: 9.4.32-3 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants