diff --git a/packages/react-router/src/ReactRouter/IonRouter.tsx b/packages/react-router/src/ReactRouter/IonRouter.tsx
index 69549956022..f1d475a733b 100644
--- a/packages/react-router/src/ReactRouter/IonRouter.tsx
+++ b/packages/react-router/src/ReactRouter/IonRouter.tsx
@@ -269,10 +269,15 @@ export const IonRouter = ({ children, registerHistoryListener }: PropsWithChildr
* tab and use its `pushedByRoute`.
*/
const lastRoute = locationHistory.current.getCurrentRouteInfoForTab(routeInfo.tab);
- // This helps maintain correct back stack behavior within tabs.
- // If this is the first time entering this tab from a different context,
- // use the leaving route's pathname as the pushedByRoute to maintain the back stack.
- routeInfo.pushedByRoute = lastRoute?.pushedByRoute ?? leavingLocationInfo.pathname;
+ /**
+ * Tab bar switches (direction 'none') should not create cross-tab back
+ * navigation. Only inherit pushedByRoute from the tab's own history.
+ */
+ if (routeInfo.routeDirection === 'none') {
+ routeInfo.pushedByRoute = lastRoute?.pushedByRoute;
+ } else {
+ routeInfo.pushedByRoute = lastRoute?.pushedByRoute ?? leavingLocationInfo.pathname;
+ }
// Triggered by `history.replace()` or a `` component, etc.
} else if (routeInfo.routeAction === 'replace') {
/**
@@ -465,10 +470,13 @@ export const IonRouter = ({ children, registerHistoryListener }: PropsWithChildr
handleNavigate(defaultHref as string, 'pop', 'back', routeAnimation);
}
/**
- * No `pushedByRoute`
- * e.g., initial page load
+ * No `pushedByRoute` (e.g., initial page load or tab root).
+ * Tabs with no back history should not navigate.
*/
} else {
+ if (routeInfo && routeInfo.tab) {
+ return;
+ }
handleNavigate(defaultHref as string, 'pop', 'back', routeAnimation);
}
};
diff --git a/packages/react-router/test/base/src/App.tsx b/packages/react-router/test/base/src/App.tsx
index 9cc461a4176..a97c04f5d71 100644
--- a/packages/react-router/test/base/src/App.tsx
+++ b/packages/react-router/test/base/src/App.tsx
@@ -40,6 +40,7 @@ import { SwipeToGoBack } from './pages/swipe-to-go-back/SwipToGoBack';
import TabsContext from './pages/tab-context/TabContext';
import Tabs from './pages/tabs/Tabs';
import TabsSecondary from './pages/tabs/TabsSecondary';
+import TabHistoryIsolation from './pages/tab-history-isolation/TabHistoryIsolation';
import Overlays from './pages/overlays/Overlays';
setupIonicReact();
@@ -66,6 +67,7 @@ const App: React.FC = () => {
} />
} />
} />
+ } />
} />
} />
} />
diff --git a/packages/react-router/test/base/src/pages/Main.tsx b/packages/react-router/test/base/src/pages/Main.tsx
index da6f3dd0667..4f87061e347 100644
--- a/packages/react-router/test/base/src/pages/Main.tsx
+++ b/packages/react-router/test/base/src/pages/Main.tsx
@@ -68,6 +68,9 @@ const Main: React.FC = () => {
Tabs
+
+ Tab History Isolation
+
Params
diff --git a/packages/react-router/test/base/src/pages/tab-history-isolation/TabHistoryIsolation.tsx b/packages/react-router/test/base/src/pages/tab-history-isolation/TabHistoryIsolation.tsx
new file mode 100644
index 00000000000..a3ea324c85c
--- /dev/null
+++ b/packages/react-router/test/base/src/pages/tab-history-isolation/TabHistoryIsolation.tsx
@@ -0,0 +1,162 @@
+import {
+ IonTabs,
+ IonRouterOutlet,
+ IonTabBar,
+ IonTabButton,
+ IonIcon,
+ IonLabel,
+ IonPage,
+ IonHeader,
+ IonToolbar,
+ IonButtons,
+ IonBackButton,
+ IonTitle,
+ IonContent,
+ IonButton,
+} from '@ionic/react';
+import { triangle, square, ellipse } from 'ionicons/icons';
+import React from 'react';
+import { Route, Navigate } from 'react-router';
+
+const TabHistoryIsolation: React.FC = () => {
+ return (
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+ Tab A
+
+
+
+ Tab B
+
+
+
+ Tab C
+
+
+
+ );
+};
+
+const TabA = () => {
+ return (
+
+
+
+
+
+
+ Tab A
+
+
+
+ Tab A
+
+ Go to A Details
+
+
+
+ );
+};
+
+const TabB = () => {
+ return (
+
+
+
+
+
+
+ Tab B
+
+
+
+ Tab B
+
+ Go to B Details
+
+
+
+ );
+};
+
+const TabC = () => {
+ return (
+
+
+
+
+
+
+ Tab C
+
+
+
+ Tab C
+
+ Go to C Details
+
+
+
+ );
+};
+
+const TabADetails = () => {
+ return (
+
+
+
+
+
+
+ Tab A Details
+
+
+ Tab A Details
+
+ );
+};
+
+const TabBDetails = () => {
+ return (
+
+
+
+
+
+
+ Tab B Details
+
+
+ Tab B Details
+
+ );
+};
+
+const TabCDetails = () => {
+ return (
+
+
+
+
+
+
+ Tab C Details
+
+
+ Tab C Details
+
+ );
+};
+
+export default TabHistoryIsolation;
diff --git a/packages/react-router/test/base/tests/e2e/specs/tab-history-isolation.cy.js b/packages/react-router/test/base/tests/e2e/specs/tab-history-isolation.cy.js
new file mode 100644
index 00000000000..84eeeb34882
--- /dev/null
+++ b/packages/react-router/test/base/tests/e2e/specs/tab-history-isolation.cy.js
@@ -0,0 +1,124 @@
+const port = 3000;
+
+describe('Tab History Isolation', () => {
+ it('should NOT navigate back to previous tab when using back button after tab bar switch', () => {
+ cy.visit(`http://localhost:${port}/tab-history-isolation/a`);
+ cy.ionPageVisible('tab-a');
+
+ cy.ionTabClick('Tab B');
+ cy.ionPageHidden('tab-a');
+ cy.ionPageVisible('tab-b');
+
+ cy.get(`div.ion-page[data-pageid=tab-b]`)
+ .find('ion-back-button')
+ .click({ force: true });
+
+ cy.wait(500);
+
+ cy.ionPageVisible('tab-b');
+ cy.ionPageHidden('tab-a');
+ cy.url().should('include', '/tab-history-isolation/b');
+ });
+
+ it('should NOT allow back navigation through multiple tab switches', () => {
+ cy.visit(`http://localhost:${port}/tab-history-isolation/a`);
+ cy.ionPageVisible('tab-a');
+
+ cy.ionTabClick('Tab B');
+ cy.ionPageHidden('tab-a');
+ cy.ionPageVisible('tab-b');
+
+ cy.ionTabClick('Tab C');
+ cy.ionPageHidden('tab-b');
+ cy.ionPageVisible('tab-c');
+
+ cy.get(`div.ion-page[data-pageid=tab-c]`)
+ .find('ion-back-button')
+ .click({ force: true });
+
+ cy.wait(500);
+
+ cy.ionPageVisible('tab-c');
+ cy.url().should('include', '/tab-history-isolation/c');
+ });
+
+ it('should navigate back within the same tab when using back button', () => {
+ cy.visit(`http://localhost:${port}/tab-history-isolation/a`);
+ cy.ionPageVisible('tab-a');
+
+ cy.get('#go-to-a-details').click();
+ cy.ionPageHidden('tab-a');
+ cy.ionPageVisible('tab-a-details');
+
+ cy.ionBackClick('tab-a-details');
+ cy.ionPageDoesNotExist('tab-a-details');
+ cy.ionPageVisible('tab-a');
+
+ cy.url().should('include', '/tab-history-isolation/a');
+ cy.url().should('not.include', '/details');
+ });
+
+ it('should only navigate back within current tab after switching tabs and navigating', () => {
+ cy.visit(`http://localhost:${port}/tab-history-isolation/a`);
+ cy.ionPageVisible('tab-a');
+
+ cy.ionTabClick('Tab B');
+ cy.ionPageHidden('tab-a');
+ cy.ionPageVisible('tab-b');
+
+ cy.get('#go-to-b-details').click();
+ cy.ionPageHidden('tab-b');
+ cy.ionPageVisible('tab-b-details');
+
+ cy.ionBackClick('tab-b-details');
+ cy.ionPageDoesNotExist('tab-b-details');
+ cy.ionPageVisible('tab-b');
+
+ cy.url().should('include', '/tab-history-isolation/b');
+ cy.url().should('not.include', '/details');
+
+ cy.get(`div.ion-page[data-pageid=tab-b]`)
+ .find('ion-back-button')
+ .click({ force: true });
+
+ cy.wait(500);
+
+ cy.ionPageVisible('tab-b');
+ cy.url().should('include', '/tab-history-isolation/b');
+ });
+
+ it('should preserve tab history when switching away and back', () => {
+ cy.visit(`http://localhost:${port}/tab-history-isolation/a`);
+ cy.ionPageVisible('tab-a');
+
+ cy.get('#go-to-a-details').click();
+ cy.ionPageHidden('tab-a');
+ cy.ionPageVisible('tab-a-details');
+
+ cy.ionTabClick('Tab B');
+ cy.ionPageHidden('tab-a-details');
+ cy.ionPageVisible('tab-b');
+
+ cy.ionTabClick('Tab A');
+ cy.ionPageHidden('tab-b');
+ cy.ionPageVisible('tab-a-details');
+
+ cy.ionBackClick('tab-a-details');
+ cy.ionPageDoesNotExist('tab-a-details');
+ cy.ionPageVisible('tab-a');
+ });
+
+ it('should have no back navigation when first visiting a tab', () => {
+ cy.visit(`http://localhost:${port}/tab-history-isolation/a`);
+ cy.ionPageVisible('tab-a');
+
+ cy.get(`div.ion-page[data-pageid=tab-a]`)
+ .find('ion-back-button')
+ .click({ force: true });
+
+ cy.wait(500);
+
+ cy.ionPageVisible('tab-a');
+ cy.url().should('include', '/tab-history-isolation/a');
+ });
+});