Skip to content

Commit 68273b8

Browse files
authored
docs: Add nested router example, format, clear up couple of issues (#83)
1 parent 0d3c249 commit 68273b8

File tree

1 file changed

+85
-43
lines changed

1 file changed

+85
-43
lines changed

README.md

Lines changed: 85 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
Isomorphic async tools for Preact.
66

7-
- Lazy-load components using `lazy()` and `<ErrorBoundary>`, which also enables progressive hydration.
8-
- Generate static HTML for your app using `prerender()`, waiting for `lazy()` components and data dependencies.
9-
- Implement async-aware client and server-side routing using `<Router>`, including seamless async transitions.
7+
- Lazy-load components using `lazy()` and `<ErrorBoundary>`, which also enables progressive hydration.
8+
- Generate static HTML for your app using `prerender()`, waiting for `lazy()` components and data dependencies.
9+
- Implement async-aware client and server-side routing using `<Router>`, including seamless async transitions.
1010

1111
## Routing
1212

@@ -30,7 +30,7 @@ const App = () => (
3030
<Home path="/" />
3131
{/* Alternative dedicated route component for better TS support */}
3232
<Route path="/profiles" component={Profiles} />
33-
<Route path="/profiles/:id" component={Profile} />
33+
<Route path="/profile/:id" component={Profile} />
3434
{/* `default` prop indicates a fallback route. Useful for 404 pages */}
3535
<NotFound default />
3636
</Router>
@@ -41,9 +41,7 @@ const App = () => (
4141

4242
**Progressive Hydration:** When the app is hydrated on the client, the route (`Home` or `Profile` in this case) suspends. This causes hydration for that part of the page to be deferred until the route's `import()` is resolved, at which point that part of the page automatically finishes hydrating.
4343

44-
**Seamless Routing:** Switch switching between routes on the client, the Router is aware of asynchronous dependencies in routes. Instead of clearing the current route and showing a loading spinner while waiting for the next route (or its data), the router preserves the current route in-place until the incoming route has finished loading, then they are swapped.
45-
46-
**Nested Routing:** Nested routes are supported by using multiple `Router` components. Partially matched routes end with a wildcard `/*` and the remaining value will be passed to continue matching with if there are any further routes.
44+
**Seamless Routing:** When switching between routes on the client, the Router is aware of asynchronous dependencies in routes. Instead of clearing the current route and showing a loading spinner while waiting for the next route, the router preserves the current route in-place until the incoming route has finished loading, then they are swapped.
4745

4846
## Prerendering
4947

@@ -74,6 +72,46 @@ export async function prerender(data) {
7472
}
7573
```
7674

75+
## Nested Routing
76+
77+
Nested routes are supported by using multiple `Router` components. Partially matched routes end with a wildcard (`/*`) and the remaining value will be passed to continue matching with if there are any further routes.
78+
79+
```js
80+
import { lazy, LocationProvider, ErrorBoundary, Router, Route } from 'preact-iso';
81+
82+
const NotFound = lazy(() => import('./routes/_404.js'));
83+
84+
const App = () => (
85+
<LocationProvider>
86+
<ErrorBoundary>
87+
<Router>
88+
<Route path="/movies/*" component={Movies} />
89+
<NotFound default />
90+
</Router>
91+
</ErrorBoundary>
92+
</LocationProvider>
93+
);
94+
95+
const TrendingMovies = lazy(() => import('./routes/movies/trending.js'));
96+
const SearchMovies = lazy(() => import('./routes/movies/search.js'));
97+
const MovieDetails = lazy(() => import('./routes/movies/details.js'));
98+
99+
const Movies = () => (
100+
<ErrorBoundary>
101+
<Router>
102+
<Route path="/trending" component={TrendingMovies} />
103+
<Route path="/search" component={SearchMovies} />
104+
<Route path="/:id" component={MovieDetails} />
105+
</Router>
106+
</ErrorBoundary>
107+
);
108+
```
109+
110+
This will match the following routes:
111+
- `/movies/trending`
112+
- `/movies/search`
113+
- `/movies/Inception`
114+
77115
---
78116

79117
## API Docs
@@ -84,7 +122,7 @@ A context provider that provides the current location to its children. This is r
84122

85123
Props:
86124

87-
- `scope?: string | RegExp` - Sets a scope for the paths that the router will handle (intercept). If a path does not match the scope, either by starting with the provided string or matching the RegExp, the router will ignore it and default browser navigation will apply.
125+
- `scope?: string | RegExp` - Sets a scope for the paths that the router will handle (intercept). If a path does not match the scope, either by starting with the provided string or matching the RegExp, the router will ignore it and default browser navigation will apply.
88126

89127
Typically, you would wrap your entire app in this provider:
90128

@@ -102,9 +140,9 @@ const App = () => (
102140

103141
Props:
104142

105-
- `onRouteChange?: (url: string) => void` - Callback to be called when a route changes.
106-
- `onLoadStart?: (url: string) => void` - Callback to be called when a route starts loading (i.e., if it suspends). This will not be called before navigations to sync routes or subsequent navigations to async routes.
107-
- `onLoadEnd?: (url: string) => void` - Callback to be called after a route finishes loading (i.e., if it suspends). This will not be called after navigations to sync routes or subsequent navigations to async routes.
143+
- `onRouteChange?: (url: string) => void` - Callback to be called when a route changes.
144+
- `onLoadStart?: (url: string) => void` - Callback to be called when a route starts loading (i.e., if it suspends). This will not be called before navigations to sync routes or subsequent navigations to async routes.
145+
- `onLoadEnd?: (url: string) => void` - Callback to be called after a route finishes loading (i.e., if it suspends). This will not be called after navigations to sync routes or subsequent navigations to async routes.
108146

109147
```js
110148
import { LocationProvider, Router } from 'preact-iso';
@@ -117,7 +155,8 @@ const App = () => (
117155
onLoadEnd={(url) => console.log('Finished loading', url)}
118156
>
119157
<Home path="/" />
120-
<Profile path="/profile" />
158+
<Profiles path="/profiles" />
159+
<Profile path="/profile/:id" />
121160
</Router>
122161
</LocationProvider>
123162
);
@@ -147,7 +186,8 @@ const App = () => (
147186
<Home path="/" />
148187
<Route path="/" component={Home} />
149188

150-
<Profile path="/profile" />
189+
<Profiles path="/profiles" />
190+
<Profile path="/profile/:id" />
151191
<NotFound default />
152192
</Router>
153193
</LocationProvider>
@@ -156,34 +196,34 @@ const App = () => (
156196

157197
Props for any route component:
158198

159-
- `path: string` - The path to match (read on)
160-
- `default?: boolean` - If set, this route is a fallback/default route to be used when nothing else matches
199+
- `path: string` - The path to match (read on)
200+
- `default?: boolean` - If set, this route is a fallback/default route to be used when nothing else matches
161201

162202
Specific to the `Route` component:
163203

164-
- `component: AnyComponent` - The component to render when the route matches
204+
- `component: AnyComponent` - The component to render when the route matches
165205

166206
#### Path Segment Matching
167207

168208
Paths are matched using a simple string matching algorithm. The following features may be used:
169209

170-
- `:param` - Matches any URL segment, binding the value to the label (can later extract this value from `useRoute()`)
171-
- `/profile/:id` will match `/profile/123` and `/profile/abc`
172-
- `/profile/:id?` will match `/profile` and `/profile/123`
173-
- `/profile/:id*` will match `/profile`, `/profile/123`, and `/profile/123/abc`
174-
- `/profile/:id+` will match `/profile/123`, `/profile/123/abc`
175-
- `*` - Matches one or more URL segments
176-
- `/profile/*` will match `/profile/123`, `/profile/123/abc`, etc.
210+
- `:param` - Matches any URL segment, binding the value to the label (can later extract this value from `useRoute()`)
211+
- `/profile/:id` will match `/profile/123` and `/profile/abc`
212+
- `/profile/:id?` will match `/profile` and `/profile/123`
213+
- `/profile/:id*` will match `/profile`, `/profile/123`, and `/profile/123/abc`
214+
- `/profile/:id+` will match `/profile/123`, `/profile/123/abc`
215+
- `*` - Matches one or more URL segments
216+
- `/profile/*` will match `/profile/123`, `/profile/123/abc`, etc.
177217

178218
These can then be composed to create more complex routes:
179219

180-
- `/profile/:id/*` will match `/profile/123/abc`, `/profile/123/abc/def`, etc.
220+
- `/profile/:id/*` will match `/profile/123/abc`, `/profile/123/abc/def`, etc.
181221

182222
The difference between `/:id*` and `/:id/*` is that in the former, the `id` param will include the entire path after it, while in the latter, the `id` is just the single path segment.
183223

184-
- `/profile/:id*`, with `/profile/123/abc`
185-
- `id` is `123/abc`
186-
- `/profile/:id/*`, with `/profile/123/abc`
224+
- `/profile/:id*`, with `/profile/123/abc`
225+
- `id` is `123/abc`
226+
- `/profile/:id/*`, with `/profile/123/abc`
187227
- `id` is `123`
188228

189229
### `useLocation`
@@ -192,21 +232,20 @@ A hook to work with the `LocationProvider` to access location context.
192232

193233
Returns an object with the following properties:
194234

195-
- `url: string` - The current path & search params
196-
- `path: string` - The current path
197-
- `query: Record<string, string>` - The current query string parameters (`/profile?name=John` -> `{ name: 'John' }`)
198-
- `route: (url: string, replace?: boolean) => void` - A function to programmatically navigate to a new route. The `replace` param can optionally be used to overwrite history, navigating them away without keeping the current location in the history stack.
235+
- `url: string` - The current path & search params
236+
- `path: string` - The current path
237+
- `query: Record<string, string>` - The current query string parameters (`/profile?name=John` -> `{ name: 'John' }`)
238+
- `route: (url: string, replace?: boolean) => void` - A function to programmatically navigate to a new route. The `replace` param can optionally be used to overwrite history, navigating them away without keeping the current location in the history stack.
199239

200240
### `useRoute`
201241

202242
A hook to access current route information. Unlike `useLocation`, this hook only works within `<Router>` components.
203243

204244
Returns an object with the following properties:
205245

206-
207-
- `path: string` - The current path
208-
- `query: Record<string, string>` - The current query string parameters (`/profile?name=John` -> `{ name: 'John' }`)
209-
- `params: Record<string, string>` - The current route parameters (`/profile/:id` -> `{ id: '123' }`)
246+
- `path: string` - The current path
247+
- `query: Record<string, string>` - The current query string parameters (`/profile?name=John` -> `{ name: 'John' }`)
248+
- `params: Record<string, string>` - The current route parameters (`/profile/:id` -> `{ id: '123' }`)
210249

211250
### `lazy`
212251

@@ -221,13 +260,15 @@ import { lazy, LocationProvider, Router } from 'preact-iso';
221260
import Home from './routes/home.js';
222261

223262
// Asynchronous, code-splitted:
224-
const Profile = lazy(() => import('./routes/profile.js'));
263+
const Profiles = lazy(() => import('./routes/profiles.js').then(m => m.Profiles)); // Expects a named export called `Profiles`
264+
const Profile = lazy(() => import('./routes/profile.js')); // Expects a default export
225265

226266
const App = () => (
227267
<LocationProvider>
228268
<Router>
229269
<Home path="/" />
230-
<Profile path="/profile" />
270+
<Profiles path="/profiles" />
271+
<Profile path="/profile/:id" />
231272
</Router>
232273
</LocationProvider>
233274
);
@@ -240,7 +281,7 @@ const Profile = lazy(() => import('./routes/profile.js'));
240281

241282
function Home() {
242283
return (
243-
<a href="/profile" onMouseOver={() => Profile.preload()}>
284+
<a href="/profile/rschristian" onMouseOver={() => Profile.preload()}>
244285
Profile Page -- Hover over me to preload the module!
245286
</a>
246287
);
@@ -253,7 +294,7 @@ A simple component to catch errors in the component tree below it.
253294

254295
Props:
255296

256-
- `onError?: (error: Error) => void` - A callback to be called when an error is caught
297+
- `onError?: (error: Error) => void` - A callback to be called when an error is caught
257298

258299
```js
259300
import { LocationProvider, ErrorBoundary, Router } from 'preact-iso';
@@ -263,7 +304,8 @@ const App = () => (
263304
<ErrorBoundary onError={(e) => console.log(e)}>
264305
<Router>
265306
<Home path="/" />
266-
<Profile path="/profile" />
307+
<Profiles path="/profiles" />
308+
<Profile path="/profile/:id" />
267309
</Router>
268310
</ErrorBoundary>
269311
</LocationProvider>
@@ -278,8 +320,8 @@ Pairs with the `prerender()` function.
278320

279321
Params:
280322

281-
- `jsx: ComponentChild` - The JSX element or component to render
282-
- `parent?: Element | Document | ShadowRoot | DocumentFragment` - The parent element to render into. Defaults to `document.body` if not provided.
323+
- `jsx: ComponentChild` - The JSX element or component to render
324+
- `parent?: Element | Document | ShadowRoot | DocumentFragment` - The parent element to render into. Defaults to `document.body` if not provided.
283325

284326
```js
285327
import { hydrate } from 'preact-iso';
@@ -303,7 +345,7 @@ Pairs primarily with [`@preact/preset-vite`](https://github.com/preactjs/preset-
303345

304346
Params:
305347

306-
- `jsx: ComponentChild` - The JSX element or component to render
348+
- `jsx: ComponentChild` - The JSX element or component to render
307349

308350
```js
309351
import { LocationProvider, ErrorBoundary, Router, lazy, prerender } from 'preact-iso';

0 commit comments

Comments
 (0)