From a8648224774c7cd55cc80690a0a559143f61294d Mon Sep 17 00:00:00 2001 From: ShaneK Date: Tue, 10 Mar 2026 13:10:55 -0700 Subject: [PATCH] fix(react-router): forward index and caseSensitive props through IonRoute --- .../src/ReactRouter/IonRouteInner.tsx | 4 +- .../src/ReactRouter/StackManager.tsx | 8 +- .../src/ReactRouter/utils/routeElements.ts | 4 +- packages/react-router/test/base/src/App.tsx | 2 + .../react-router/test/base/src/pages/Main.tsx | 3 + .../pages/ion-route-props/IonRouteProps.tsx | 78 +++++++++++++++++++ .../tests/e2e/specs/ion-route-props.cy.js | 42 ++++++++++ packages/react/src/components/IonRoute.tsx | 2 + 8 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 packages/react-router/test/base/src/pages/ion-route-props/IonRouteProps.tsx create mode 100644 packages/react-router/test/base/tests/e2e/specs/ion-route-props.cy.js diff --git a/packages/react-router/src/ReactRouter/IonRouteInner.tsx b/packages/react-router/src/ReactRouter/IonRouteInner.tsx index 4d3a440df8..8fea15b50d 100644 --- a/packages/react-router/src/ReactRouter/IonRouteInner.tsx +++ b/packages/react-router/src/ReactRouter/IonRouteInner.tsx @@ -2,6 +2,6 @@ import type { IonRouteProps } from '@ionic/react'; import React from 'react'; import { Route } from 'react-router-dom'; -export const IonRouteInner = ({ path, element }: IonRouteProps) => { - return ; +export const IonRouteInner = ({ path, index, caseSensitive, element }: IonRouteProps) => { + return ; }; diff --git a/packages/react-router/src/ReactRouter/StackManager.tsx b/packages/react-router/src/ReactRouter/StackManager.tsx index 9894813764..6cba702f36 100644 --- a/packages/react-router/src/ReactRouter/StackManager.tsx +++ b/packages/react-router/src/ReactRouter/StackManager.tsx @@ -5,7 +5,7 @@ */ import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react'; -import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react'; +import { IonRoute, RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react'; import React from 'react'; import { Route } from 'react-router-dom'; @@ -258,7 +258,8 @@ export class StackManager extends React.PureComponent { const routesChildren = getRoutesChildren(this.ionRouterOutlet.props.children) ?? this.ionRouterOutlet.props.children; const routeChildren = React.Children.toArray(routesChildren).filter( - (child): child is React.ReactElement => React.isValidElement(child) && child.type === Route + (child): child is React.ReactElement => + React.isValidElement(child) && (child.type === Route || child.type === IonRoute) ); const hasRelativeRoutes = routeChildren.some((route) => { @@ -1306,7 +1307,8 @@ function findRouteByRouteInfo(node: React.ReactNode, routeInfo: RouteInfo, paren // Collect all route children const routeChildren = React.Children.toArray(routesChildren).filter( - (child): child is React.ReactElement => React.isValidElement(child) && child.type === Route + (child): child is React.ReactElement => + React.isValidElement(child) && (child.type === Route || child.type === IonRoute) ); // Sort routes by specificity (most specific first) diff --git a/packages/react-router/src/ReactRouter/utils/routeElements.ts b/packages/react-router/src/ReactRouter/utils/routeElements.ts index e0e52dd620..9d749fe135 100644 --- a/packages/react-router/src/ReactRouter/utils/routeElements.ts +++ b/packages/react-router/src/ReactRouter/utils/routeElements.ts @@ -1,3 +1,4 @@ +import { IonRoute } from '@ionic/react'; import React from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; @@ -33,7 +34,8 @@ export const getRoutesChildren = (node: React.ReactNode): React.ReactNode | unde export const extractRouteChildren = (children: React.ReactNode): React.ReactElement[] => { const routesChildren = getRoutesChildren(children) ?? children; return React.Children.toArray(routesChildren).filter( - (child): child is React.ReactElement => React.isValidElement(child) && child.type === Route + (child): child is React.ReactElement => + React.isValidElement(child) && (child.type === Route || child.type === IonRoute) ); }; diff --git a/packages/react-router/test/base/src/App.tsx b/packages/react-router/test/base/src/App.tsx index 9ebd4c2e78..b342ea05d4 100644 --- a/packages/react-router/test/base/src/App.tsx +++ b/packages/react-router/test/base/src/App.tsx @@ -47,6 +47,7 @@ import NestedTabsRelativeLinks from './pages/nested-tabs-relative-links/NestedTa import RootSplatTabs from './pages/root-splat-tabs/RootSplatTabs'; import ContentChangeNavigation from './pages/content-change-navigation/ContentChangeNavigation'; import SearchParams from './pages/search-params/SearchParams'; +import IonRoutePropsTest from './pages/ion-route-props/IonRouteProps'; setupIonicReact(); @@ -83,6 +84,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 e901613b27..2791289139 100644 --- a/packages/react-router/test/base/src/pages/Main.tsx +++ b/packages/react-router/test/base/src/pages/Main.tsx @@ -86,6 +86,9 @@ const Main: React.FC = () => { Search Params + + IonRoute Props + diff --git a/packages/react-router/test/base/src/pages/ion-route-props/IonRouteProps.tsx b/packages/react-router/test/base/src/pages/ion-route-props/IonRouteProps.tsx new file mode 100644 index 0000000000..7e13f22d94 --- /dev/null +++ b/packages/react-router/test/base/src/pages/ion-route-props/IonRouteProps.tsx @@ -0,0 +1,78 @@ +import { + IonButton, + IonContent, + IonHeader, + IonPage, + IonRouterOutlet, + IonTitle, + IonToolbar, + IonRoute, +} from '@ionic/react'; +import React from 'react'; +import { Route } from 'react-router-dom'; + +/** + * Test page for IonRoute index and caseSensitive props. + * + * Verifies that: + * 1. renders as the default when navigating to the parent path + * 2. correctly enforces case-sensitive path matching + */ + +const IndexHome: React.FC = () => ( + + + + Index Home + + + +

This is the index route

+ + Go to Details + +
+
+); + +const Details: React.FC = () => ( + + + + Details + + + +

Details page

+ + Back to Home + +
+
+); + +const CaseSensitivePage: React.FC = () => ( + + + + Case Sensitive Match + + + +

Matched case-sensitive route

+
+
+); + +const IonRoutePropsTest: React.FC = () => ( + + {/* Index route: should render when navigating to /ion-route-props */} + } /> + } /> + } /> + {/* Catch-all for unmatched routes */} + } /> + +); + +export default IonRoutePropsTest; diff --git a/packages/react-router/test/base/tests/e2e/specs/ion-route-props.cy.js b/packages/react-router/test/base/tests/e2e/specs/ion-route-props.cy.js new file mode 100644 index 0000000000..bfd0409eea --- /dev/null +++ b/packages/react-router/test/base/tests/e2e/specs/ion-route-props.cy.js @@ -0,0 +1,42 @@ +const port = 3000; + +describe('IonRoute Props', () => { + /** + * Tests that IonRoute correctly forwards index and caseSensitive props. + * + * Previously, IonRouteInner only destructured path and element, + * silently dropping index and caseSensitive props. + */ + + describe('index prop', () => { + it('should render the index route when navigating to parent path', () => { + cy.visit(`http://localhost:${port}/ion-route-props`); + cy.ionPageVisible('index-home'); + cy.get('#index-home-text').should('be.visible').and('contain', 'This is the index route'); + }); + + it('should navigate from index route to details and back', () => { + cy.visit(`http://localhost:${port}/ion-route-props`); + cy.ionPageVisible('index-home'); + cy.ionNav('ion-button', 'Go to Details'); + cy.ionPageVisible('ion-route-details'); + cy.get('#details-text').should('be.visible'); + cy.ionNav('ion-button', 'Back to Home'); + cy.ionPageVisible('index-home'); + }); + }); + + describe('caseSensitive prop', () => { + it('should match the exact case path', () => { + cy.visit(`http://localhost:${port}/ion-route-props/CaseSensitive`); + cy.ionPageVisible('case-sensitive-page'); + cy.get('#case-sensitive-text').should('be.visible'); + }); + + it('should not match a different case path when caseSensitive is true', () => { + cy.visit(`http://localhost:${port}/ion-route-props/casesensitive`); + // The case-sensitive page should NOT be visible since the case doesn't match + cy.get('[data-pageid="case-sensitive-page"]').should('not.exist'); + }); + }); +}); diff --git a/packages/react/src/components/IonRoute.tsx b/packages/react/src/components/IonRoute.tsx index 3bacaac2e1..29e1708fd6 100644 --- a/packages/react/src/components/IonRoute.tsx +++ b/packages/react/src/components/IonRoute.tsx @@ -4,6 +4,8 @@ import { NavContext } from '../contexts/NavContext'; export interface IonRouteProps { path?: string; + index?: boolean; + caseSensitive?: boolean; show?: boolean; element: React.ReactElement; disableIonPageManagement?: boolean;