Skip to content

Commit 348bee9

Browse files
committed
fix(react-router): set pushedByRoute for routerDirection=none so back button uses history instead of defaultHref
1 parent fa00be4 commit 348bee9

9 files changed

Lines changed: 146 additions & 8 deletions

File tree

packages/react-router/src/ReactRouter/IonRouter.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,21 @@ export const IonRouter = ({ children, registerHistoryListener }: PropsWithChildr
344344
// This preserves tab context for same-tab navigation while allowing cross-tab navigation.
345345
routeInfo.tab = routeInfo.tab || leavingLocationInfo.tab;
346346
routeInfo.pushedByRoute = leavingLocationInfo.pathname;
347-
// Triggered by a browser back button or handleNavigateBack.
347+
} else if (
348+
routeInfo.routeAction === 'push' &&
349+
routeInfo.routeDirection === 'none' &&
350+
routeInfo.tab === leavingLocationInfo.tab
351+
) {
352+
/**
353+
* Push with routerDirection="none" within the same tab (or non-tab) context.
354+
* Still needs pushedByRoute so the back button can navigate back correctly.
355+
* Cross-tab navigations with direction "none" are handled by the tab-switching
356+
* block below which has different pushedByRoute semantics.
357+
*/
358+
routeInfo.tab = routeInfo.tab || leavingLocationInfo.tab;
359+
routeInfo.pushedByRoute = leavingLocationInfo.pathname;
348360
} else if (routeInfo.routeAction === 'pop') {
361+
// Triggered by a browser back button or handleNavigateBack.
349362
// Find the route that pushed this one.
350363
const r = locationHistory.current.findLastLocation(routeInfo);
351364
routeInfo.pushedByRoute = r?.pushedByRoute;

packages/react-router/test/base/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import RouteContextShape from './pages/route-context-shape/RouteContextShape';
5858
import ModalAriaHidden from './pages/modal-aria-hidden/ModalAriaHidden';
5959
import RedirectParams from './pages/redirect-params/RedirectParams';
6060
import MultiStepBack from './pages/multi-step-back/MultiStepBack';
61+
import DirectionNoneBack from './pages/direction-none-back/DirectionNoneBack';
6162

6263
setupIonicReact();
6364

@@ -105,6 +106,7 @@ const App: React.FC = () => {
105106
<Route path="/modal-aria-hidden/*" element={<ModalAriaHidden />} />
106107
<Route path="/redirect-params/*" element={<RedirectParams />} />
107108
<Route path="/multi-step-back/*" element={<MultiStepBack />} />
109+
<Route path="/direction-none-back/*" element={<DirectionNoneBack />} />
108110
</IonRouterOutlet>
109111
</IonReactRouter>
110112
</IonApp>

packages/react-router/test/base/src/pages/Main.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ const Main: React.FC = () => {
119119
<IonItem routerLink="/multi-step-back/a">
120120
<IonLabel>Multi-Step Back</IonLabel>
121121
</IonItem>
122+
<IonItem routerLink="/direction-none-back/a">
123+
<IonLabel>Direction None Back</IonLabel>
124+
</IonItem>
122125
</IonList>
123126
</IonContent>
124127
</IonPage>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import {
2+
IonContent,
3+
IonHeader,
4+
IonPage,
5+
IonTitle,
6+
IonToolbar,
7+
IonButton,
8+
IonRouterOutlet,
9+
IonBackButton,
10+
IonButtons,
11+
} from '@ionic/react';
12+
import React from 'react';
13+
import { Route, Navigate } from 'react-router-dom';
14+
15+
/**
16+
* Tests that IonBackButton works correctly after navigating with
17+
* routerDirection="none". The back button should use history to
18+
* determine the previous page, not fall back to defaultHref.
19+
*/
20+
const PageA: React.FC = () => {
21+
return (
22+
<IonPage data-pageid="direction-none-page-a">
23+
<IonHeader>
24+
<IonToolbar>
25+
<IonTitle>Page A</IonTitle>
26+
</IonToolbar>
27+
</IonHeader>
28+
<IonContent>
29+
<IonButton id="go-forward" routerLink="/direction-none-back/b">
30+
Go to B (forward)
31+
</IonButton>
32+
<IonButton id="go-none" routerLink="/direction-none-back/b" routerDirection="none">
33+
Go to B (none)
34+
</IonButton>
35+
</IonContent>
36+
</IonPage>
37+
);
38+
};
39+
40+
const PageB: React.FC = () => {
41+
return (
42+
<IonPage data-pageid="direction-none-page-b">
43+
<IonHeader>
44+
<IonToolbar>
45+
<IonButtons slot="start">
46+
<IonBackButton defaultHref="/direction-none-back/fallback" />
47+
</IonButtons>
48+
<IonTitle>Page B</IonTitle>
49+
</IonToolbar>
50+
</IonHeader>
51+
<IonContent>
52+
<p>Page B content</p>
53+
</IonContent>
54+
</IonPage>
55+
);
56+
};
57+
58+
const Fallback: React.FC = () => {
59+
return (
60+
<IonPage data-pageid="direction-none-fallback">
61+
<IonHeader>
62+
<IonToolbar>
63+
<IonTitle>Fallback (defaultHref)</IonTitle>
64+
</IonToolbar>
65+
</IonHeader>
66+
<IonContent>
67+
<p>This page should NOT be reached via back button if history exists</p>
68+
</IonContent>
69+
</IonPage>
70+
);
71+
};
72+
73+
const DirectionNoneBack: React.FC = () => {
74+
return (
75+
<IonRouterOutlet>
76+
<Route index element={<Navigate to="/direction-none-back/a" replace />} />
77+
<Route path="a" element={<PageA />} />
78+
<Route path="b" element={<PageB />} />
79+
<Route path="fallback" element={<Fallback />} />
80+
</IonRouterOutlet>
81+
);
82+
};
83+
84+
export default DirectionNoneBack;

packages/react-router/test/base/src/pages/dynamic-ionpage-classnames/DynamicIonpageClassnames.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import React, { useEffect, useRef, useState } from 'react';
44
/**
55
* Test page for verifying that dynamically changing className on IonPage
66
* preserves framework-added classes (can-go-back, ion-page-invisible, etc.).
7-
*
8-
* Related issue: https://github.com/ionic-team/ionic-framework/issues/22631
97
*/
108
const DynamicIonpageClassnames: React.FC = () => {
119
return <Page />;

packages/react-router/test/base/src/pages/multi-step-back/MultiStepBack.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import { Route, useNavigate } from 'react-router-dom';
1616
* Tests for navigate(-n) where n > 1 (multi-step back navigation).
1717
* Verifies that the correct view is shown when skipping multiple
1818
* entries in the history stack.
19-
*
20-
* @see https://github.com/ionic-team/ionic-framework/issues/23775
2119
*/
2220
const PageA: React.FC = () => {
2321
const navigate = useNavigate();

packages/react-router/test/base/src/pages/redirect-params/RedirectParams.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
/**
22
* Verifies that useParams() returns correct values after a Navigate
33
* (catch-all redirect) fires inside IonRouterOutlet.
4-
*
5-
* @see https://github.com/ionic-team/ionic-framework/issues/23743
64
*/
75

86
import {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const port = 3000;
2+
3+
/**
4+
* Tests that IonBackButton works correctly after navigating with
5+
* routerDirection="none". The back button should use history to
6+
* determine the previous page, not fall back to defaultHref.
7+
*
8+
* @see https://github.com/ionic-team/ionic-framework/issues/24074
9+
*/
10+
describe('routerDirection="none" Back Button', () => {
11+
12+
it('back button should return to Page A after navigating with direction "forward"', () => {
13+
cy.visit(`http://localhost:${port}/direction-none-back/a`);
14+
cy.ionPageVisible('direction-none-page-a');
15+
16+
// Navigate A -> B with default forward direction
17+
cy.ionNav('ion-button#go-forward', 'Go to B (forward)');
18+
cy.ionPageVisible('direction-none-page-b');
19+
20+
// Back button should go back to Page A (not to defaultHref fallback)
21+
cy.ionBackClick('direction-none-page-b');
22+
cy.ionPageDoesNotExist('direction-none-fallback');
23+
cy.ionPageVisible('direction-none-page-a');
24+
cy.url().should('include', '/direction-none-back/a');
25+
});
26+
27+
it('back button should return to Page A after navigating with direction "none"', () => {
28+
cy.visit(`http://localhost:${port}/direction-none-back/a`);
29+
cy.ionPageVisible('direction-none-page-a');
30+
31+
// Navigate A -> B with routerDirection="none"
32+
cy.ionNav('ion-button#go-none', 'Go to B (none)');
33+
cy.ionPageVisible('direction-none-page-b');
34+
35+
// Back button should go back to Page A (not to defaultHref fallback)
36+
cy.ionBackClick('direction-none-page-b');
37+
cy.ionPageDoesNotExist('direction-none-fallback');
38+
cy.ionPageVisible('direction-none-page-a');
39+
cy.url().should('include', '/direction-none-back/a');
40+
});
41+
42+
});

packages/react-router/test/base/tests/e2e/specs/multi-step-back.cy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const port = 3000;
77
*
88
* @see https://github.com/ionic-team/ionic-framework/issues/23775
99
*/
10-
describe('Multi-Step Back Navigation (#23775)', () => {
10+
describe('Multi-Step Back Navigation', () => {
1111

1212
it('A > B > C > navigate(-2) should show Page A', () => {
1313
cy.visit(`http://localhost:${port}/multi-step-back/a`);

0 commit comments

Comments
 (0)