Skip to content

Commit 150ba3a

Browse files
ShaneKthetaPCsean-perkins
authored
feat(react-router): upgrade to react router 6 (#30831)
Issue number: resolves #24177 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? Currently, Ionic Framework React Router only supports React Router 5. This has many issues and unsupported/broken features. ## What is the new behavior? With this change, Ionic Framework will support React Router 6 while still supporting transitions in the same way a native app does. Most of what caused this change to take a long time is that React Router 5 and React Router 6 have fundamental differences in how they handle components once they're no longer part of the view. In this change, we move away from relying on React Router directly so much and have our own implementation for deciding how views get dealt with during navigation and when they're cleaned up, allowing for us to still transition between them like we need to while still using React Router as much as we possibly can. This change will also lay the foundation for the migration to React Router 7, which will ideally be easier since most of the hard work has been dealt with here. ## Does this introduce a breaking change? - [X] Yes - [ ] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Current dev build (last updated 2026-04-27): > **⚠️ WARNING:** If you're going to use this dev build on an existing react project, you'll need to migrate to React Router 6. Migrating a large project at this point might be a bad idea since this will not release until v9, which will require further migrations and have other breaking changes! I have a preview of migration documentation for this [here](https://ionic-docs-git-v9-react-router-ionic1.vercel.app/docs/updating/9-0#react-router). ``` 8.8.4-dev.11777318673.18d001f6 ``` The dev build linked above will be the last one for this branch alone. Everything going forward will be the major-9.0 branch, which may include large breaking changes. --------- Co-authored-by: Maria Hutt <thetaPC@users.noreply.github.com> Co-authored-by: Sean Perkins <13732623+sean-perkins@users.noreply.github.com>
1 parent 346048b commit 150ba3a

192 files changed

Lines changed: 22326 additions & 5437 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/actions/test-react-router-e2e/action.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ runs:
2929
shell: bash
3030
working-directory: ./packages/react-router/test
3131
- name: 🕸️ Install Dependencies
32-
run: npm install
32+
run: npm install --legacy-peer-deps
33+
shell: bash
34+
working-directory: ./packages/react-router/test/build/${{ inputs.app }}
35+
- name: 📦 Install Playwright Browsers
36+
run: npx playwright install chromium
3337
shell: bash
3438
working-directory: ./packages/react-router/test/build/${{ inputs.app }}
3539
- name: 🔄 Sync Built Changes
@@ -40,7 +44,13 @@ runs:
4044
run: npm run build
4145
shell: bash
4246
working-directory: ./packages/react-router/test/build/${{ inputs.app }}
43-
- name: 🧪 Run Tests
47+
- name: 🧪 Run Cypress Tests
4448
run: npm run e2e
4549
shell: bash
4650
working-directory: ./packages/react-router/test/build/${{ inputs.app }}
51+
- name: 🎭 Run Playwright Tests
52+
run: npx playwright test --retries=2
53+
env:
54+
CI: true
55+
shell: bash
56+
working-directory: ./packages/react-router/test/build/${{ inputs.app }}

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ jobs:
176176
strategy:
177177
fail-fast: false
178178
matrix:
179-
apps: [reactrouter5]
179+
apps: [reactrouter6-react18, reactrouter6-react19]
180180
needs: [build-react, build-react-router]
181181
runs-on: ubuntu-latest
182182
steps:

.github/workflows/stencil-nightly.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ jobs:
186186
strategy:
187187
fail-fast: false
188188
matrix:
189-
apps: [reactrouter5]
189+
apps: [reactrouter6-react18, reactrouter6-react19]
190190
needs: [build-react, build-react-router]
191191
runs-on: ubuntu-latest
192192
steps:

BREAKING.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ This is a comprehensive list of the breaking changes introduced in the major ver
1818
- [Components](#version-9x-components)
1919
- [Legacy Picker](#version-9x-legacy-picker)
2020
- [Router Outlet](#version-9x-router-outlet)
21+
- [Framework Specific](#version-9x-framework-specific)
22+
- [React](#version-9x-react)
2123

2224
<h2 id="version-9x-browser-platform-support">Browser and Platform Support</h2>
2325

@@ -68,3 +70,184 @@ To disable the gesture on a specific outlet, set `swipeGesture` to `false`:
6870
```
6971

7072
The `swipeBackEnabled` config option is still respected as the initial default and does not need to change for apps that set it once at startup.
73+
74+
<h2 id="version-9x-framework-specific">Framework Specific</h2>
75+
76+
<h4 id="version-9x-react">React</h4>
77+
78+
The `@ionic/react-router` package now requires React Router v6. React Router v5 is no longer supported.
79+
80+
**Minimum Version Requirements**
81+
| Package | Supported Version |
82+
| ---------------- | ----------------- |
83+
| react-router | 6.0.0+ |
84+
| react-router-dom | 6.0.0+ |
85+
86+
React Router v6 introduces several API changes that will require updates to your application's routing configuration:
87+
88+
**Route Definition Changes**
89+
90+
The `component` prop has been replaced with the `element` prop, which accepts JSX:
91+
92+
```diff
93+
- <Route path="/home" component={Home} exact />
94+
+ <Route path="/home" element={<Home />} />
95+
```
96+
97+
**Redirect Changes**
98+
99+
The `<Redirect>` component has been replaced with `<Navigate>`:
100+
101+
```diff
102+
- import { Redirect } from 'react-router-dom';
103+
+ import { Navigate } from 'react-router-dom';
104+
105+
- <Redirect to="/home" />
106+
+ <Navigate to="/home" replace />
107+
```
108+
109+
**Nested Route Paths**
110+
111+
Routes that contain nested routes or child `IonRouterOutlet` components need a `/*` suffix to match sub-paths:
112+
113+
```diff
114+
- <Route path="/tabs" element={<Tabs />} />
115+
+ <Route path="/tabs/*" element={<Tabs />} />
116+
```
117+
118+
**Accessing Route Parameters**
119+
120+
Route parameters are now accessed via the `useParams` hook instead of props:
121+
122+
```diff
123+
- import { RouteComponentProps } from 'react-router-dom';
124+
+ import { useParams } from 'react-router-dom';
125+
126+
- const MyComponent: React.FC<RouteComponentProps<{ id: string }>> = ({ match }) => {
127+
- const id = match.params.id;
128+
+ const MyComponent: React.FC = () => {
129+
+ const { id } = useParams<{ id: string }>();
130+
```
131+
132+
**RouteComponentProps Removed**
133+
134+
The `RouteComponentProps` type and its `history`, `location`, and `match` props are no longer available in React Router v6. Use the equivalent hooks instead:
135+
136+
- `history` -> `useNavigate` (see below) or `useIonRouter`
137+
- `match.params` -> `useParams` (covered above)
138+
- `location` -> `useLocation`
139+
140+
```diff
141+
- import { RouteComponentProps } from 'react-router-dom';
142+
+ import { useNavigate, useLocation } from 'react-router-dom';
143+
+ import { useIonRouter } from '@ionic/react';
144+
145+
- const MyComponent: React.FC<RouteComponentProps> = ({ history, location }) => {
146+
- history.push('/path');
147+
- history.replace('/path');
148+
- history.goBack();
149+
- console.log(location.pathname);
150+
+ const MyComponent: React.FC = () => {
151+
+ const navigate = useNavigate();
152+
+ const router = useIonRouter();
153+
+ const location = useLocation();
154+
+ // In an event handler or useEffect:
155+
+ navigate('/path');
156+
+ navigate('/path', { replace: true });
157+
+ router.goBack();
158+
+ console.log(location.pathname);
159+
```
160+
161+
**Exact Prop Removed**
162+
163+
The `exact` prop is no longer needed. React Router v6 routes match exactly by default. To match sub-paths, use a `/*` suffix on the path:
164+
165+
```diff
166+
- <Route path="/home" exact />
167+
+ <Route path="/home" />
168+
```
169+
170+
**Render Prop Removed**
171+
172+
The `render` prop has been replaced with the `element` prop:
173+
174+
```diff
175+
- <Route path="/foo" render={(props) => <Foo {...props} />} />
176+
+ <Route path="/foo" element={<Foo />} />
177+
```
178+
179+
**Programmatic Navigation**
180+
181+
The `useHistory` hook has been replaced with `useNavigate`:
182+
183+
```diff
184+
- import { useHistory } from 'react-router-dom';
185+
+ import { useNavigate } from 'react-router-dom';
186+
+ import { useIonRouter } from '@ionic/react';
187+
188+
- const history = useHistory();
189+
+ const navigate = useNavigate();
190+
+ const router = useIonRouter();
191+
192+
- history.push('/path');
193+
+ navigate('/path');
194+
195+
- history.replace('/path');
196+
+ navigate('/path', { replace: true });
197+
198+
- history.goBack();
199+
+ router.goBack();
200+
```
201+
202+
**Custom History Prop Removed**
203+
204+
The `history` prop has been removed from `IonReactRouter`, `IonReactHashRouter`, and `IonReactMemoryRouter`. React Router v6's `BrowserRouter`, `HashRouter`, and `MemoryRouter` no longer accept custom `history` objects.
205+
206+
```diff
207+
- import { createBrowserHistory } from 'history';
208+
- const history = createBrowserHistory();
209+
- <IonReactRouter history={history}>
210+
+ <IonReactRouter>
211+
```
212+
213+
For `IonReactMemoryRouter` (commonly used in tests), use `initialEntries` instead:
214+
215+
```diff
216+
- import { createMemoryHistory } from 'history';
217+
- const history = createMemoryHistory({ initialEntries: ['/start'] });
218+
- <IonReactMemoryRouter history={history}>
219+
+ <IonReactMemoryRouter initialEntries={['/start']}>
220+
```
221+
222+
**IonRedirect Removed**
223+
224+
The `IonRedirect` component has been removed. Use React Router's `<Navigate>` component instead:
225+
226+
```diff
227+
- import { IonRedirect } from '@ionic/react';
228+
- <IonRedirect path="/old" to="/new" exact />
229+
+ import { Navigate } from 'react-router-dom';
230+
+ <Route path="/old" element={<Navigate to="/new" replace />} />
231+
```
232+
233+
**Path Regex Constraints Removed**
234+
235+
React Router v6 no longer supports regex constraints in path parameters (e.g., `/:tab(sessions)`). Use literal paths instead:
236+
237+
```diff
238+
- <Route path="/:tab(sessions)" component={SessionsPage} />
239+
- <Route path="/:tab(sessions)/:id" component={SessionDetail} />
240+
+ <Route path="/sessions" element={<SessionsPage />} />
241+
+ <Route path="/sessions/:id" element={<SessionDetail />} />
242+
```
243+
244+
**IonRoute API Changes**
245+
246+
The `IonRoute` component follows the same API changes as React Router's `<Route>`. The `render` prop has been replaced with `element`, and the `exact` prop has been removed:
247+
248+
```diff
249+
- <IonRoute path="/foo" exact render={(props) => <Foo {...props} />} />
250+
+ <IonRoute path="/foo" element={<Foo />} />
251+
```
252+
253+
For more information on migrating from React Router v5 to v6, refer to the [React Router v6 Upgrade Guide](https://reactrouter.com/6.28.0/upgrading/v5).

0 commit comments

Comments
 (0)