Skip to content

Commit 433768a

Browse files
committed
fix(react-router): symmetric pathname+search url comparison
The leavingUrl/currentUrl comparison in handleHistoryChange used `leavingLocationInfo.pathname + leavingLocationInfo.search` on the left side but `location.pathname` (no search) on the right. For any route with a non-empty search string, the comparison was always unequal, so the transition block ran on every history event — including no-op popstates over same-URL entries (e.g. pushed via window.history.pushState). That triggered a false POP transition to the previous route while the browser URL stayed put. Compares pathname+search on both sides so the block runs only when the URL actually changed. Verified with a new Cypress regression that pushes a same-URL state on a search-bearing route, calls history.back(), and asserts the page does not teleport.
1 parent c88c0de commit 433768a

2 files changed

Lines changed: 21 additions & 1 deletion

File tree

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
107107
}
108108

109109
const leavingUrl = leavingLocationInfo.pathname + leavingLocationInfo.search;
110-
if (leavingUrl !== location.pathname) {
110+
const currentUrl = location.pathname + (location.search || '');
111+
if (leavingUrl !== currentUrl) {
111112
if (!this.incomingRouteParams) {
112113
if (action === 'REPLACE') {
113114
this.incomingRouteParams = {

packages/react-router/test/base/tests/e2e/specs/routing.cy.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,25 @@ describe('Routing Tests', () => {
344344
cy.get('div.ion-page[data-pageid=home-details-page-1] [data-testid="details-input"]').should('have.value', '1');
345345
});
346346

347+
it('Details 1 with Query Params > pop a same-URL history entry, should stay on details 1', () => {
348+
// Regression: popstate over a same-URL entry on a search-bearing route used to trigger a false POP transition.
349+
cy.visit(`http://localhost:${port}/routing`);
350+
cy.ionNav('ion-item', 'Details 1 with Query Params');
351+
cy.ionPageVisible('home-details-page-1');
352+
cy.location('search').should('eq', '?hello=there');
353+
354+
cy.window().then((win) => {
355+
win.history.pushState({ marker: true }, '', win.location.href);
356+
});
357+
358+
cy.go('back');
359+
360+
// No teleport: still on details 1, URL unchanged.
361+
cy.ionPageVisible('home-details-page-1');
362+
cy.location('pathname').should('eq', '/routing/tabs/home/details/1');
363+
cy.location('search').should('eq', '?hello=there');
364+
});
365+
347366
/*
348367
Tests to add:
349368
Test that lifecycle events fire

0 commit comments

Comments
 (0)