Skip to content

Commit d2401a0

Browse files
committed
docs(react): fix nested IonRouterOutlet examples and replace live example
1 parent 76552ed commit d2401a0

7 files changed

Lines changed: 271 additions & 39 deletions

File tree

docs/react/navigation.md

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ sidebar_label: Navigation/Routing
44
---
55

66
import useBaseUrl from '@docusaurus/useBaseUrl';
7+
import NavigationPlayground from '@site/static/usage/v9/react/navigation/index.md';
78

89
<head>
910
<title>React Navigation: Router Link Redirect to Navigate to Another Page</title>
@@ -57,20 +58,18 @@ Inside the Dashboard page, we define more routes related to this specific sectio
5758
**DashboardPage.tsx**
5859

5960
```tsx
60-
const DashboardPage: React.FC = () => {
61-
return (
62-
<IonPage>
63-
<IonRouterOutlet>
64-
<Route index element={<UsersListPage />} />
65-
<Route path="users/:id" element={<UserDetailPage />} />
66-
</IonRouterOutlet>
67-
</IonPage>
68-
);
69-
};
61+
const DashboardPage: React.FC = () => (
62+
<IonRouterOutlet ionPage>
63+
<Route index element={<UsersListPage />} />
64+
<Route path="users/:id" element={<UserDetailPage />} />
65+
</IonRouterOutlet>
66+
);
7067
```
7168

7269
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.
7370

71+
Note the `ionPage` prop on `IonRouterOutlet`. When a component serves as a nested outlet rendered directly by a `Route` in a parent outlet, the inner `IonRouterOutlet` must include the `ionPage` prop. Without it, router outlets can overlap during navigation and cause broken transitions. Wrapping the outlet in an `IonPage` is not needed and should be avoided in this case.
72+
7473
These routes are grouped in an `IonRouterOutlet`, let's discuss that next.
7574

7675
## IonRouterOutlet
@@ -90,35 +89,27 @@ We can define a fallback route by placing a `Route` component with a `path` of `
9089
**DashboardPage.tsx**
9190

9291
```tsx
93-
const DashboardPage: React.FC = () => {
94-
return (
95-
<IonPage>
96-
<IonRouterOutlet>
97-
<Route index element={<UsersListPage />} />
98-
<Route path="users/:id" element={<UserDetailPage />} />
99-
<Route path="*" element={<Navigate to="/dashboard" replace />} />
100-
</IonRouterOutlet>
101-
</IonPage>
102-
);
103-
};
92+
const DashboardPage: React.FC = () => (
93+
<IonRouterOutlet ionPage>
94+
<Route index element={<UsersListPage />} />
95+
<Route path="users/:id" element={<UserDetailPage />} />
96+
<Route path="*" element={<Navigate to="/dashboard" replace />} />
97+
</IonRouterOutlet>
98+
);
10499
```
105100

106101
Here, we see that in the event a location does not match the first two `Route`s the `IonRouterOutlet` will redirect the Ionic React app to the `/dashboard` path.
107102

108103
You can alternatively supply a component to render instead of providing a redirect.
109104

110105
```tsx
111-
const DashboardPage: React.FC = () => {
112-
return (
113-
<IonPage>
114-
<IonRouterOutlet>
115-
<Route index element={<UsersListPage />} />
116-
<Route path="users/:id" element={<UserDetailPage />} />
117-
<Route path="*" element={<NotFoundPage />} />
118-
</IonRouterOutlet>
119-
</IonPage>
120-
);
121-
};
106+
const DashboardPage: React.FC = () => (
107+
<IonRouterOutlet ionPage>
108+
<Route index element={<UsersListPage />} />
109+
<Route path="users/:id" element={<UserDetailPage />} />
110+
<Route path="*" element={<NotFoundPage />} />
111+
</IonRouterOutlet>
112+
);
122113
```
123114

124115
## IonPage
@@ -351,12 +342,10 @@ const App: React.FC = () => (
351342
);
352343

353344
const DashboardRouterOutlet: React.FC = () => (
354-
<IonPage>
355-
<IonRouterOutlet>
356-
<Route index element={<DashboardMainPage />} />
357-
<Route path="stats" element={<DashboardStatsPage />} />
358-
</IonRouterOutlet>
359-
</IonPage>
345+
<IonRouterOutlet ionPage>
346+
<Route index element={<DashboardMainPage />} />
347+
<Route path="stats" element={<DashboardStatsPage />} />
348+
</IonRouterOutlet>
360349
);
361350
```
362351

@@ -509,7 +498,7 @@ The example below shows how the Spotify app reuses the same album component to s
509498

510499
## Live Example
511500

512-
If you would prefer to get hands on with the concepts and code described above, please checkout our [live example](https://stackblitz.com/edit/ionic-react-routing?file=src/index.tsx) of the topics above on StackBlitz.
501+
<NavigationPlayground />
513502

514503
### IonRouterOutlet in a Tabs View
515504

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Navigation</title>
7+
<link rel="stylesheet" href="../../common.css" />
8+
<script src="../../common.js"></script>
9+
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core@8/dist/ionic/ionic.esm.js"></script>
10+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core@8/css/ionic.bundle.css" />
11+
</head>
12+
13+
<body>
14+
<ion-app>
15+
<ion-tabs>
16+
<ion-tab tab="dashboard">
17+
<ion-nav id="dashboard-nav"></ion-nav>
18+
<div id="dashboard-page">
19+
<ion-header>
20+
<ion-toolbar>
21+
<ion-title>Dashboard</ion-title>
22+
</ion-toolbar>
23+
</ion-header>
24+
<ion-content>
25+
<ion-list>
26+
<ion-item button id="item-1">
27+
<ion-label>Item One</ion-label>
28+
</ion-item>
29+
<ion-item button id="item-2">
30+
<ion-label>Item Two</ion-label>
31+
</ion-item>
32+
<ion-item button id="item-3">
33+
<ion-label>Item Three</ion-label>
34+
</ion-item>
35+
</ion-list>
36+
</ion-content>
37+
</div>
38+
</ion-tab>
39+
<ion-tab tab="settings">
40+
<ion-nav id="settings-nav"></ion-nav>
41+
<div id="settings-page">
42+
<ion-header>
43+
<ion-toolbar>
44+
<ion-title>Settings</ion-title>
45+
</ion-toolbar>
46+
</ion-header>
47+
<ion-content class="ion-padding">Settings content</ion-content>
48+
</div>
49+
</ion-tab>
50+
<ion-tab-bar slot="bottom">
51+
<ion-tab-button tab="dashboard">
52+
<ion-icon name="grid-outline"></ion-icon>
53+
<ion-label>Dashboard</ion-label>
54+
</ion-tab-button>
55+
<ion-tab-button tab="settings">
56+
<ion-icon name="settings-outline"></ion-icon>
57+
<ion-label>Settings</ion-label>
58+
</ion-tab-button>
59+
</ion-tab-bar>
60+
</ion-tabs>
61+
</ion-app>
62+
63+
<script>
64+
const dashboardNav = document.querySelector('#dashboard-nav');
65+
const dashboardPage = document.querySelector('#dashboard-page');
66+
dashboardNav.root = dashboardPage;
67+
68+
const settingsNav = document.querySelector('#settings-nav');
69+
const settingsPage = document.querySelector('#settings-page');
70+
settingsNav.root = settingsPage;
71+
72+
function createDetailPage(itemId) {
73+
const page = document.createElement('div');
74+
page.innerHTML = `
75+
<ion-header>
76+
<ion-toolbar>
77+
<ion-buttons slot="start">
78+
<ion-back-button default-href="/dashboard"></ion-back-button>
79+
</ion-buttons>
80+
<ion-title>Item ${itemId}</ion-title>
81+
</ion-toolbar>
82+
</ion-header>
83+
<ion-content class="ion-padding">You navigated to item ${itemId}.</ion-content>
84+
`;
85+
return page;
86+
}
87+
88+
['1', '2', '3'].forEach((id) => {
89+
document.querySelector(`#item-${id}`).addEventListener('click', () => {
90+
dashboardNav.push(createDetailPage(id));
91+
});
92+
});
93+
</script>
94+
</body>
95+
</html>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Playground from '@site/src/components/global/Playground';
2+
3+
import react_main_tsx from './react/main_tsx.md';
4+
import react_dashboard_page_tsx from './react/dashboard_page_tsx.md';
5+
import react_item_detail_page_tsx from './react/item_detail_page_tsx.md';
6+
import react_settings_page_tsx from './react/settings_page_tsx.md';
7+
8+
<Playground
9+
version="9"
10+
src="usage/v9/react/navigation/demo.html"
11+
code={{
12+
react: {
13+
files: {
14+
'src/main.tsx': react_main_tsx,
15+
'src/pages/DashboardPage.tsx': react_dashboard_page_tsx,
16+
'src/pages/ItemDetailPage.tsx': react_item_detail_page_tsx,
17+
'src/pages/SettingsPage.tsx': react_settings_page_tsx,
18+
},
19+
},
20+
}}
21+
size="xlarge"
22+
includeIonContent={false}
23+
/>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
```tsx
2+
import React from 'react';
3+
import {
4+
IonContent,
5+
IonHeader,
6+
IonItem,
7+
IonLabel,
8+
IonList,
9+
IonPage,
10+
IonTitle,
11+
IonToolbar,
12+
} from '@ionic/react';
13+
14+
const items = [
15+
{ id: '1', name: 'Item One' },
16+
{ id: '2', name: 'Item Two' },
17+
{ id: '3', name: 'Item Three' },
18+
];
19+
20+
const DashboardPage: React.FC = () => (
21+
<IonPage>
22+
<IonHeader>
23+
<IonToolbar>
24+
<IonTitle>Dashboard</IonTitle>
25+
</IonToolbar>
26+
</IonHeader>
27+
<IonContent>
28+
<IonList>
29+
{items.map((item) => (
30+
<IonItem key={item.id} routerLink={`/dashboard/${item.id}`}>
31+
<IonLabel>{item.name}</IonLabel>
32+
</IonItem>
33+
))}
34+
</IonList>
35+
</IonContent>
36+
</IonPage>
37+
);
38+
39+
export default DashboardPage;
40+
```
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
```tsx
2+
import React from 'react';
3+
import {
4+
IonBackButton,
5+
IonButtons,
6+
IonContent,
7+
IonHeader,
8+
IonPage,
9+
IonTitle,
10+
IonToolbar,
11+
} from '@ionic/react';
12+
import { useParams } from 'react-router-dom';
13+
14+
const ItemDetailPage: React.FC = () => {
15+
const { id } = useParams<{ id: string }>();
16+
17+
return (
18+
<IonPage>
19+
<IonHeader>
20+
<IonToolbar>
21+
<IonButtons slot="start">
22+
<IonBackButton defaultHref="/dashboard" />
23+
</IonButtons>
24+
<IonTitle>Item {id}</IonTitle>
25+
</IonToolbar>
26+
</IonHeader>
27+
<IonContent className="ion-padding">You navigated to item {id}.</IonContent>
28+
</IonPage>
29+
);
30+
};
31+
32+
export default ItemDetailPage;
33+
```
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
```tsx
2+
import React from 'react';
3+
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
4+
import { IonReactRouter } from '@ionic/react-router';
5+
import { Route, Navigate } from 'react-router-dom';
6+
import { gridOutline, settingsOutline } from 'ionicons/icons';
7+
import DashboardPage from './pages/DashboardPage';
8+
import ItemDetailPage from './pages/ItemDetailPage';
9+
import SettingsPage from './pages/SettingsPage';
10+
11+
const Example: React.FC = () => (
12+
<IonReactRouter>
13+
<IonTabs>
14+
<IonRouterOutlet>
15+
<Route path="/dashboard" element={<DashboardPage />} />
16+
<Route path="/dashboard/:id" element={<ItemDetailPage />} />
17+
<Route path="/settings" element={<SettingsPage />} />
18+
<Route path="/" element={<Navigate to="/dashboard" replace />} />
19+
</IonRouterOutlet>
20+
<IonTabBar slot="bottom">
21+
<IonTabButton tab="dashboard" href="/dashboard">
22+
<IonIcon icon={gridOutline} />
23+
<IonLabel>Dashboard</IonLabel>
24+
</IonTabButton>
25+
<IonTabButton tab="settings" href="/settings">
26+
<IonIcon icon={settingsOutline} />
27+
<IonLabel>Settings</IonLabel>
28+
</IonTabButton>
29+
</IonTabBar>
30+
</IonTabs>
31+
</IonReactRouter>
32+
);
33+
34+
export default Example;
35+
```
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
```tsx
2+
import React from 'react';
3+
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
4+
5+
const SettingsPage: React.FC = () => (
6+
<IonPage>
7+
<IonHeader>
8+
<IonToolbar>
9+
<IonTitle>Settings</IonTitle>
10+
</IonToolbar>
11+
</IonHeader>
12+
<IonContent className="ion-padding">Settings content</IonContent>
13+
</IonPage>
14+
);
15+
16+
export default SettingsPage;
17+
```

0 commit comments

Comments
 (0)