@@ -657,9 +657,25 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
657657 this . ionPageWaitTimeout = undefined ;
658658 }
659659 this . pendingPageTransition = false ;
660+
660661 const foundView = this . context . findViewItemByRouteInfo ( routeInfo , this . id ) ;
661662 if ( foundView ) {
662663 const oldPageElement = foundView . ionPageElement ;
664+
665+ /**
666+ * FIX for issue #28878: Reject orphaned IonPage registrations.
667+ *
668+ * When a component conditionally renders different IonPages (e.g., list vs empty state)
669+ * using React keys, and state changes simultaneously with navigation, the new IonPage
670+ * tries to register for a route we're navigating away from. This creates a stale view.
671+ *
672+ * Only reject if both pageIds exist and differ, to allow nested outlet registrations.
673+ */
674+ if ( this . shouldRejectOrphanedPage ( page , oldPageElement , routeInfo ) ) {
675+ this . hideAndRemoveOrphanedPage ( page ) ;
676+ return ;
677+ }
678+
663679 foundView . ionPageElement = page ;
664680 foundView . ionRoute = true ;
665681
@@ -675,6 +691,45 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
675691 this . handlePageTransition ( routeInfo ) ;
676692 }
677693
694+ /**
695+ * Determines if a new IonPage registration should be rejected as orphaned.
696+ * This happens when a component re-renders with a different IonPage while navigating away.
697+ */
698+ private shouldRejectOrphanedPage (
699+ newPage : HTMLElement ,
700+ oldPageElement : HTMLElement | undefined ,
701+ routeInfo : RouteInfo
702+ ) : boolean {
703+ if ( ! oldPageElement || oldPageElement === newPage ) {
704+ return false ;
705+ }
706+
707+ const newPageId = newPage . getAttribute ( 'data-pageid' ) ;
708+ const oldPageId = oldPageElement . getAttribute ( 'data-pageid' ) ;
709+
710+ // Only reject if both pageIds exist and are different
711+ if ( ! newPageId || ! oldPageId || newPageId === oldPageId ) {
712+ return false ;
713+ }
714+
715+ // Reject only if we're navigating away from this route
716+ return this . props . routeInfo . pathname !== routeInfo . pathname ;
717+ }
718+
719+ /**
720+ * Hides an orphaned IonPage and schedules its removal from the DOM.
721+ */
722+ private hideAndRemoveOrphanedPage ( page : HTMLElement ) : void {
723+ page . classList . add ( 'ion-page-hidden' ) ;
724+ page . setAttribute ( 'aria-hidden' , 'true' ) ;
725+
726+ setTimeout ( ( ) => {
727+ if ( page . parentElement ) {
728+ page . remove ( ) ;
729+ }
730+ } , VIEW_UNMOUNT_DELAY_MS ) ;
731+ }
732+
678733 /**
679734 * Configures the router outlet for the swipe-to-go-back gesture.
680735 *
0 commit comments