Skip to content

fix(react-router): keep splat route page mounted when a sibling route is pushed - #31481

Open
ShaneK wants to merge 6 commits into
mainfrom
fix/rr6-route-bug
Open

ShaneK wants to merge 6 commits into
mainfrom
fix/rr6-route-bug

Conversation

@ShaneK

@ShaneK ShaneK commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Issue number: resolves #31477


What is the current behavior?

Currently, an outlet holding a splat route alongside a more specific sibling loses the splat's page when you push that sibling. A splat matches every pathname, so findViewItems hands back its view item for a pathname the sibling owns, and handlePageTransition then overwrites that item's reactElement, which swaps the page and unmounts it. Whatever was underneath, usually tabs or a nested outlet, is destroyed along with its state and scroll position.

A root-level /* has two further problems. Going back blanks the whole outlet, because getParentPath caches an inferred outletMountPath on the root outlet, which scopes it to whatever route was active and makes the next pathname look out of scope, so handleOutOfScopeOutlet aborts the transition and tears the outlet down. Swipe-to-go-back does nothing, because the deactivation scan in renderViewItem re-applies ion-page-hidden on the next render and undoes revealIonPageForSwipeBack, leaving the user dragging a page with display: none.

What is the new behavior?

handlePageTransition now compares the view item it found against findRouteByRouteInfo, which is React Router's own ranking, and drops it when the two disagree so a fresh view item gets created for the winning route. The catch-all deactivation no longer unmounts a view that was pushed over, since hiding it is enough to keep it from rendering alongside the pushed page. A root outlet no longer caches a mount path, in both places that were doing it, because it is mounted under nothing and an inferred path only scopes it wrongly. The deactivation scan also skips whichever page a swipe gesture is currently revealing, tracked in a WeakSet that is marked in onStart and cleared when the gesture ends.

Does this introduce a breaking change?

  • Yes
  • No

Other information

New test pages:

Current Dev Build

9.0.6-dev.11790349282.1a877abd

@ShaneK
ShaneK requested a review from a team as a code owner September 24, 2026 20:11
@ShaneK
ShaneK requested a review from OS-jacobbell September 24, 2026 20:11
@vercel

vercel Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ionic-framework Ready Ready Preview Sep 25, 2026 3:54pm UTC

Request Review

@thetaPC thetaPC 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.

LGTM

Comment on lines +1164 to +1165
isOverMatchingRoute(enteringViewItem.routeData?.childProps ?? {}) &&
enteringRoute.props.path !== enteringViewItem.routeData?.childProps?.path

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.

These make the if statement large. Maybe consider making them into variables.

This branch was successfully deployed

1 active deployment
Preview — d77e2805 Deployed Sep 25, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: react @ionic/react package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: react-router, a /* route is overwritten by a more specific sibling route and its page is unmounted

2 participants