Skip to content

Commit 8fcf1cf

Browse files
committed
docs(react-router): update navigation guide and expand v5-to-v6 migration guide
1 parent 2b442d1 commit 8fcf1cf

File tree

2 files changed

+193
-63
lines changed

2 files changed

+193
-63
lines changed

docs/react/navigation.md

Lines changed: 54 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,28 @@ const App: React.FC = () => (
3030
<IonApp>
3131
<IonReactRouter>
3232
<IonRouterOutlet>
33-
<Route path="/dashboard" element={<DashboardPage />} />
33+
<Route path="/dashboard/*" element={<DashboardPage />} />
3434
<Route path="/" element={<Navigate to="/dashboard" replace />} />
3535
</IonRouterOutlet>
3636
</IonReactRouter>
3737
</IonApp>
3838
);
3939
```
4040

41-
Directly after the `Route`, we define our default `Navigate`, which, when a user visits the root URL of the app ("/"), redirects them to the "/dashboard" URL.
41+
Directly after the `Route`, we define our default `Navigate`, which, when a user visits the root URL of the app ("/"), redirects them to the "/dashboard" URL. Note the `/*` suffix on the dashboard route. This allows the nested routes inside `DashboardPage` to match sub-paths like `/dashboard/users/:id`.
4242

4343
You can also conditionally redirect based on a condition, like checking if a user is authenticated or not:
4444

4545
```tsx
4646
<Route
47-
path="/dashboard"
47+
path="/dashboard/*"
4848
element={isAuthed ? <DashboardPage /> : <Navigate to="/login" replace />}
4949
/>
5050
```
5151

5252
## IonReactRouter
5353

54-
The `IonReactRouter` component wraps the traditional [`BrowserRouter`](https://reactrouter.com/en/main/router-components/browser-router) component from React Router, and sets the app up for routing. Therefore, use `IonReactRouter` in place of `BrowserRouter`. You can pass in any props to `IonReactRouter` and they will be passed down to the underlying `BrowserRouter`.
54+
The `IonReactRouter` component wraps the traditional [`BrowserRouter`](https://reactrouter.com/6.28.0/router-components/browser-router) component from React Router, and sets the app up for routing. Therefore, use `IonReactRouter` in place of `BrowserRouter`. You can pass in any props to `IonReactRouter` and they will be passed down to the underlying `BrowserRouter`.
5555

5656
## Nested Routes
5757

@@ -64,15 +64,15 @@ const DashboardPage: React.FC = () => {
6464
return (
6565
<IonPage>
6666
<IonRouterOutlet>
67-
<Route path="/dashboard" element={<UsersListPage />} />
68-
<Route path="/dashboard/users/:id" element={<UserDetailPage />} />
67+
<Route index element={<UsersListPage />} />
68+
<Route path="users/:id" element={<UserDetailPage />} />
6969
</IonRouterOutlet>
7070
</IonPage>
7171
);
7272
};
7373
```
7474

75-
Here, there are a couple more routes defined to point to pages from within the dashboard portion of the app. Note, that we need to define the whole route in the path, and we can't leave off "/dashboard" even though we arrived to this page from that URL. React Router requires full paths, and relative paths are not supported.
75+
Since the parent route already matches `/dashboard/*`, the child routes use **relative paths**. The `index` route matches the parent path (`/dashboard`) and `"users/:id"` resolves to `/dashboard/users/:id`. Absolute paths (e.g., `path="/dashboard/users/:id"`) still work if you prefer explicit full paths.
7676

7777
These routes are grouped in an `IonRouterOutlet`, let's discuss that next.
7878

@@ -95,11 +95,13 @@ We can define a fallback route by placing a `Route` component with a `path` of `
9595
```tsx
9696
const DashboardPage: React.FC = () => {
9797
return (
98-
<IonRouterOutlet>
99-
<Route path="/dashboard" element={<UsersListPage />} />
100-
<Route path="/dashboard/users/:id" element={<UserDetailPage />} />
101-
<Route path="*" element={<Navigate to="/dashboard" replace />} />
102-
</IonRouterOutlet>
98+
<IonPage>
99+
<IonRouterOutlet>
100+
<Route index element={<UsersListPage />} />
101+
<Route path="users/:id" element={<UserDetailPage />} />
102+
<Route path="*" element={<Navigate to="/dashboard" replace />} />
103+
</IonRouterOutlet>
104+
</IonPage>
103105
);
104106
};
105107
```
@@ -111,11 +113,13 @@ You can alternatively supply a component to render instead of providing a redire
111113
```tsx
112114
const DashboardPage: React.FC = () => {
113115
return (
114-
<IonRouterOutlet>
115-
<Route path="/dashboard" element={<UsersListPage />} />
116-
<Route path="/dashboard/users/:id" element={<UserDetailPage />} />
117-
<Route path="*" element={<NotFoundPage />} />
118-
</IonRouterOutlet>
116+
<IonPage>
117+
<IonRouterOutlet>
118+
<Route index element={<UsersListPage />} />
119+
<Route path="users/:id" element={<UserDetailPage />} />
120+
<Route path="*" element={<NotFoundPage />} />
121+
</IonRouterOutlet>
122+
</IonPage>
119123
);
120124
};
121125
```
@@ -177,15 +181,15 @@ Other components that have the `routerLink` prop are `IonButton`, `IonCard`, `Io
177181

178182
Each of these components also have a `routerDirection` prop to explicitly set the type of page transition to use (`"forward"`, `"back"`, or `"root"`).
179183

180-
Outside of these components that have the `routerLink` prop, you can also use React Router's [`Link`](https://reactrouter.com/en/main/components/link) component to navigate between views:
184+
Outside of these components that have the `routerLink` prop, you can also use React Router's [`Link`](https://reactrouter.com/6.28.0/components/link) component to navigate between views:
181185

182186
```html
183187
<Link to="/dashboard/users/1">User 1</Link>
184188
```
185189

186190
We recommend using one of the above methods whenever possible for routing. The advantage to these approaches is that they both render an anchor (`<a>`)tag, which is suitable for overall app accessibility.
187191

188-
For programmatic navigation, use the `useIonRouter` hook (see [Utilities](#useionrouter)) or React Router's [`useNavigate`](https://reactrouter.com/en/main/hooks/use-navigate) hook:
192+
For programmatic navigation, use the `useIonRouter` hook (see [Utilities](#useionrouter)) or React Router's [`useNavigate`](https://reactrouter.com/6.28.0/hooks/use-navigate) hook:
189193

190194
```tsx
191195
import { useNavigate } from 'react-router-dom';
@@ -208,15 +212,15 @@ const MyComponent: React.FC = () => {
208212

209213
### Navigating using `navigate` with delta
210214

211-
React Router's `navigate` function can accept a delta number to move forward or backward through the application history. Let's take a look at an example.
215+
React Router's `navigate` function can accept a delta number to move forward or backward through the application history.
212216

213217
Say you have the following application history:
214218

215219
`/pageA` --> `/pageB` --> `/pageC`
216220

217221
If you were to call `navigate(-2)` on `/pageC`, you would be brought back to `/pageA`. If you then called `navigate(2)`, you would be brought to `/pageC`.
218222

219-
Using `navigate()` with delta values in Ionic React is not supported at the moment. Interested in seeing support for this get added to Ionic React? [Let us know on GitHub](https://github.com/ionic-team/ionic-framework/issues/23775)!
223+
Using `navigate()` with delta values is not recommended in Ionic React because it follows the browser's linear history, which does not account for Ionic's non-linear tab and nested outlet navigation stacks. Use the `useIonRouter` hook's [`goBack()`](#useionrouter) method instead, which navigates within the current Ionic navigation stack.
220224

221225
## URL Parameters
222226

@@ -243,7 +247,7 @@ const UserDetailPage: React.FC = () => {
243247
};
244248
```
245249

246-
The [`useParams`](https://reactrouter.com/en/main/hooks/use-params) hook returns an object containing the URL parameters. We obtain the `id` param here and display it on the screen.
250+
The [`useParams`](https://reactrouter.com/6.28.0/hooks/use-params) hook returns an object containing the URL parameters. We obtain the `id` param here and display it on the screen.
247251

248252
Note how we use a TypeScript generic to strongly type the params object. This gives us type safety and code completion inside of the component.
249253

@@ -297,7 +301,7 @@ From here, we switch to the `Search` tab. Then, we tap the `Originals` tab again
297301

298302
Why is this non-linear routing? The previous view we were on was the `Search` view. However, pressing the back button on the `Ted Lasso` view should bring us back to the root `Originals` view. This happens because each tab in a mobile app is treated as its own stack. The [Working with Tabs](#working-with-tabs) sections goes over this in more detail.
299303

300-
If tapping the back button simply called `history.go(-1)` from the `Ted Lasso` view, we would be brought back to the `Search` view which is not correct.
304+
If tapping the back button simply called `navigate(-1)` from the `Ted Lasso` view, we would be brought back to the `Search` view which is not correct.
301305

302306
Non-linear routing allows for sophisticated user flows that linear routing cannot handle. However, certain linear routing APIs such as `navigate()` with delta values cannot be used in this non-linear environment. This means that `navigate(-1)` or similar delta navigation should not be used when using tabs or nested outlets.
303307

@@ -332,7 +336,7 @@ const App: React.FC = () => (
332336
);
333337
```
334338

335-
The above routes are considered "shared" because they reuse the `dashboard` piece of the URL.
339+
The above routes are considered "shared" because they reuse the `dashboard` piece of the URL. Since these routes are flat siblings in the same `IonRouterOutlet` (not nested), they don't need a `/*` suffix.
336340

337341
### Nested Routes
338342

@@ -350,14 +354,16 @@ const App: React.FC = () => (
350354
);
351355

352356
const DashboardRouterOutlet: React.FC = () => (
353-
<IonRouterOutlet>
354-
<Route path="/dashboard" element={<DashboardMainPage />} />
355-
<Route path="/dashboard/stats" element={<DashboardStatsPage />} />
356-
</IonRouterOutlet>
357+
<IonPage>
358+
<IonRouterOutlet>
359+
<Route index element={<DashboardMainPage />} />
360+
<Route path="stats" element={<DashboardStatsPage />} />
361+
</IonRouterOutlet>
362+
</IonPage>
357363
);
358364
```
359365

360-
The above routes are nested because they are in the `children` array of the parent route. Notice that the parent route renders the `DashboardRouterOutlet` component and uses a `/*` suffix to match all sub-paths. When you nest routes, you need to render another instance of `IonRouterOutlet`.
366+
The above routes are nested because they are rendered inside the `DashboardRouterOutlet` component, which is a child of the parent route. The parent route uses a `/*` suffix to match all sub-paths, and the nested `IonRouterOutlet` renders the appropriate child route.
361367

362368
### Which one should I choose?
363369

@@ -397,10 +403,10 @@ import Tab3 from './pages/Tab3';
397403
const Tabs: React.FC = () => (
398404
<IonTabs>
399405
<IonRouterOutlet>
400-
<Route path="/tabs/tab1" element={<Tab1 />} />
401-
<Route path="/tabs/tab2" element={<Tab2 />} />
402-
<Route path="/tabs/tab3" element={<Tab3 />} />
403-
<Route path="/tabs" element={<Navigate to="/tabs/tab1" replace />} />
406+
<Route path="tab1" element={<Tab1 />} />
407+
<Route path="tab2" element={<Tab2 />} />
408+
<Route path="tab3" element={<Tab3 />} />
409+
<Route index element={<Navigate to="tab1" replace />} />
404410
</IonRouterOutlet>
405411
<IonTabBar slot="bottom">
406412
<IonTabButton tab="tab1" href="/tabs/tab1">
@@ -422,7 +428,7 @@ const Tabs: React.FC = () => (
422428
export default Tabs;
423429
```
424430

425-
If you have worked with Ionic Framework before, this should feel familiar. We create an `IonTabs` component and provide an `IonTabBar`. The `IonTabBar` provides `IonTabButton` components, each with a `tab` property that is associated with its corresponding tab in the router config. We also provide an `IonRouterOutlet` to give `IonTabs` an outlet to render the different tab views in.
431+
If you have worked with Ionic Framework before, this should feel familiar. We create an `IonTabs` component and provide an `IonTabBar`. The `IonTabBar` provides `IonTabButton` components, each with a `tab` property that is associated with its corresponding tab in the router config. We also provide an `IonRouterOutlet` to give `IonTabs` an outlet to render the different tab views in. Note how the `Route` paths are relative (e.g., `"tab1"` instead of `"/tabs/tab1"`) since the parent route already matches `/tabs/*`.
426432

427433
:::tip
428434
`IonTabs` renders an `IonPage` for you, so you do not need to add `IonPage` manually here.
@@ -438,16 +444,16 @@ Since Ionic is focused on helping developers build mobile apps, the tabs in Ioni
438444

439445
### Child Routes within Tabs
440446

441-
When adding additional routes to tabs you should write them as sibling routes with the parent tab as the path prefix. The example below defines the `/tabs/tab1/view` route as a sibling of the `/tabs/tab1` route. Since this new route has the `tab1` prefix, it will be rendered inside of the `Tabs` component, and Tab 1 will still be selected in the `IonTabBar`.
447+
When adding additional routes to tabs you should write them as sibling routes with the parent tab as the path prefix. The example below defines the `tab1/view` route as a sibling of the `tab1` route. Since this new route has the `tab1` prefix, it will be rendered inside of the `Tabs` component, and Tab 1 will still be selected in the `IonTabBar`.
442448

443449
```tsx
444450
<IonTabs>
445451
<IonRouterOutlet>
446-
<Route path="/tabs/tab1" element={<Tab1 />} />
447-
<Route path="/tabs/tab1/view" element={<Tab1View />} />
448-
<Route path="/tabs/tab2" element={<Tab2 />} />
449-
<Route path="/tabs/tab3" element={<Tab3 />} />
450-
<Route path="/tabs" element={<Navigate to="/tabs/tab1" replace />} />
452+
<Route path="tab1" element={<Tab1 />} />
453+
<Route path="tab1/view" element={<Tab1View />} />
454+
<Route path="tab2" element={<Tab2 />} />
455+
<Route path="tab3" element={<Tab3 />} />
456+
<Route index element={<Navigate to="tab1" replace />} />
451457
</IonRouterOutlet>
452458
<IonTabBar slot="bottom">
453459
<IonTabButton tab="tab1" href="/tabs/tab1">
@@ -510,29 +516,25 @@ If you would prefer to get hands on with the concepts and code described above,
510516

511517
### IonRouterOutlet in a Tabs View
512518

513-
When working in a tabs view, Ionic React needs a way to determine what views belong to which tabs. We accomplish this by taking advantage of the fact that the paths provided to a `Route` are regular expressions.
514-
515-
While the syntax looks a bit strange, it is reasonably straightforward once you understand it.
519+
When working in a tabs view, Ionic React needs a way to determine what views belong to which tabs. It does this by matching the path prefix of each route.
516520

517521
For example, the routes for a view with two tabs (sessions and speakers) can be set up as such:
518522

519523
```tsx
520524
<IonRouterOutlet>
521-
<Route path="/:tab(sessions)" element={<SessionsPage />} />
522-
<Route path="/:tab(sessions)/:id" element={<SessionDetail />} />
523-
<Route path="/:tab(speakers)" element={<SpeakerList />} />
525+
<Route path="sessions" element={<SessionsPage />} />
526+
<Route path="sessions/:id" element={<SessionDetail />} />
527+
<Route path="speakers" element={<SpeakerList />} />
524528
</IonRouterOutlet>
525529
```
526530

527-
If the navigated URL were "/sessions", it would match the first route and add a URL parameter named "tab" with the value of "sessions". You can access this parameter using the `useParams` hook.
528-
529-
When a user navigates to a session detail page ("/sessions/1" for instance), the second route adds a URL parameter named "tab" with a value of "sessions". When `IonRouterOutlet` sees that both pages are in the same "sessions" tab, it provides an animated page transition to the new view. If a user navigates to a new tab ("speakers" in this case), `IonRouterOutlet` knows not to provide the animation.
531+
When a user navigates to a session detail page ("/sessions/1" for instance), `IonRouterOutlet` sees that both the list and detail pages share the same "sessions" path prefix and provides an animated page transition to the new view. If a user navigates to a different tab ("speakers" in this case), `IonRouterOutlet` knows not to provide the animation.
530532

531533
## Utilities
532534

533535
### useIonRouter
534536

535-
The `useIonRouter` hook can be used for more direct control over routing in Ionic React. It allows you to pass additional metadata to Ionic, such as a custom animation, before calling React Router.
537+
The `useIonRouter` hook gives you more control over routing in Ionic React, including custom page transition animations and Ionic-aware back navigation via `goBack()`.
536538

537539
The `useIonRouter` hook returns a `UseIonRouterResult` which has several convenience methods for routing:
538540

@@ -559,7 +561,7 @@ type UseIonRouterResult = {
559561
*/
560562
goBack(animationBuilder?: AnimationBuilder): void;
561563
/**
562-
* Determines if there are any additional routes in the the Router's history. However, routing is not prevented if the browser's history has more entries. Returns true if more entries exist, false if not.
564+
* Determines if there are any additional routes in the Router's history. However, routing is not prevented if the browser's history has more entries. Returns true if more entries exist, false if not.
563565
*/
564566
canGoBack(): boolean;
565567
/**
@@ -587,4 +589,4 @@ const MyComponent: React.FC = () => {
587589

588590
## More Information
589591

590-
For more info on routing in React using the React Router implementation that Ionic uses under the hood, check out their docs at [https://reactrouter.com](https://reactrouter.com).
592+
For more info on routing in React using the React Router implementation that Ionic uses under the hood, check out their docs at [https://reactrouter.com/6.28.0](https://reactrouter.com/6.28.0).

0 commit comments

Comments
 (0)