@solidjs/router 1.0.0-next.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +715 -531
- package/dist/components.d.ts +31 -0
- package/dist/components.jsx +40 -0
- package/dist/data/action.d.ts +11 -40
- package/dist/data/action.js +100 -293
- package/dist/data/createAsync.d.ts +32 -0
- package/dist/data/createAsync.js +96 -0
- package/dist/data/events.d.ts +0 -8
- package/dist/data/events.js +23 -24
- package/dist/data/index.d.ts +4 -2
- package/dist/data/index.js +4 -2
- package/dist/data/query.d.ts +3 -1
- package/dist/data/query.js +22 -39
- package/dist/data/response.d.ts +4 -0
- package/dist/data/response.js +42 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.js +1024 -1597
- package/dist/index.jsx +2 -2
- package/dist/lifecycle.d.ts +4 -29
- package/dist/lifecycle.js +37 -40
- package/dist/routers/HashRouter.d.ts +9 -0
- package/dist/routers/HashRouter.js +41 -0
- package/dist/routers/MemoryRouter.d.ts +24 -0
- package/dist/routers/MemoryRouter.js +57 -0
- package/dist/routers/Router.d.ts +17 -0
- package/dist/routers/Router.js +59 -0
- package/dist/routers/StaticRouter.d.ts +6 -0
- package/dist/routers/StaticRouter.js +15 -0
- package/dist/routers/components.d.ts +26 -12
- package/dist/routers/components.jsx +54 -68
- package/dist/routers/createRouter.d.ts +10 -0
- package/dist/routers/createRouter.js +41 -0
- package/dist/routers/index.d.ts +11 -4
- package/dist/routers/index.js +6 -2
- package/dist/routers/scrollRestoration.d.ts +32 -0
- package/dist/routers/scrollRestoration.js +96 -0
- package/dist/routing.d.ts +52 -88
- package/dist/routing.js +166 -381
- package/dist/types.d.ts +33 -83
- package/dist/utils.d.ts +0 -2
- package/dist/utils.js +0 -2
- package/package.json +7 -11
- package/dist/claims.d.ts +0 -21
- package/dist/claims.js +0 -115
- package/dist/data/flash.d.ts +0 -21
- package/dist/data/flash.js +0 -78
- package/dist/data/flashCookie.d.ts +0 -7
- package/dist/data/flashCookie.js +0 -20
- package/dist/data/serverForms.d.ts +0 -1
- package/dist/data/serverForms.js +0 -5
- package/dist/paths.d.ts +0 -117
- package/dist/paths.js +0 -41
- package/dist/routers/factory.d.ts +0 -45
- package/dist/routers/factory.jsx +0 -143
- package/dist/routers/history.d.ts +0 -24
- package/dist/routers/history.js +0 -180
- package/dist/server.d.ts +0 -75
- package/dist/server.js +0 -264
package/README.md
CHANGED
|
@@ -10,683 +10,947 @@
|
|
|
10
10
|
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
|
-
**Solid Router** brings fine-grained reactivity to route navigation
|
|
13
|
+
**Solid Router** brings fine-grained reactivity to route navigation, enabling your single-page application to become multi-paged without full page reloads. Fully integrated into the SolidJS ecosystem, Solid Router provides declarative syntax with features like universal rendering and parallel data fetching for best performance.
|
|
14
14
|
|
|
15
15
|
Explore the official [documentation](https://docs.solidjs.com/solid-router) for detailed guides and examples.
|
|
16
16
|
|
|
17
17
|
## Core Features
|
|
18
18
|
|
|
19
|
-
- **
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
- **
|
|
19
|
+
- **All Routing Modes**:
|
|
20
|
+
- [History-Based](https://docs.solidjs.com/solid-router/reference/components/router#router) for standard browser navigation
|
|
21
|
+
- [Hash-Based](https://docs.solidjs.com/solid-router/reference/components/hash-router#hashrouter) for navigation based on URL hash
|
|
22
|
+
- [Static Routing](https://docs.solidjs.com/solid-router/rendering-modes/ssr#server-side-rendering) for server-side rendering (_SSR_)
|
|
23
|
+
- [Memory-Based](https://docs.solidjs.com/solid-router/reference/components/memory-router#memoryrouter) for testing in non-browser environments
|
|
24
|
+
- **TypeScript**: Full integration for robust, type-safe development
|
|
25
|
+
- **Universal Rendering**: Seamless rendering on both client and server environments
|
|
26
|
+
- **Declarative**: Define routes as components or as an object
|
|
27
|
+
- **Preload Functions**: Parallel data fetching, following the render-as-you-fetch pattern
|
|
28
|
+
- **Dynamic Route Parameters**: Flexible URL patterns with parameters, optional segments, and wildcards
|
|
29
|
+
- **Data APIs with Caching**: Reactive data fetching with deduplication and revalidation
|
|
25
30
|
|
|
26
|
-
## Table of
|
|
31
|
+
## Table of contents
|
|
27
32
|
|
|
28
33
|
- [Getting Started](#getting-started)
|
|
29
|
-
- [
|
|
30
|
-
- [
|
|
31
|
-
- [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- [Nested Routes](#nested-routes)
|
|
37
|
-
- [Lazy Route Subtrees](#lazy-route-subtrees)
|
|
38
|
-
- [Typed Paths](#typed-paths)
|
|
39
|
-
- [Links](#links)
|
|
40
|
-
- [Preload Functions](#preload-functions)
|
|
34
|
+
- [Set Up the Router](#set-up-the-router)
|
|
35
|
+
- [Configure Your Routes](#configure-your-routes)
|
|
36
|
+
- [Create Links to Your Routes](#create-links-to-your-routes)
|
|
37
|
+
- [Dynamic Routes](#dynamic-routes)
|
|
38
|
+
- [Nested Routes](#nested-routes)
|
|
39
|
+
- [Hash Mode Router](#hash-mode-router)
|
|
40
|
+
- [Memory Mode Router](#memory-mode-router)
|
|
41
41
|
- [Data APIs](#data-apis)
|
|
42
|
-
- [
|
|
43
|
-
- [
|
|
42
|
+
- [Config Based Routing](#config-based-routing)
|
|
43
|
+
- [Components](#components)
|
|
44
44
|
- [Router Primitives](#router-primitives)
|
|
45
|
-
- [
|
|
46
|
-
- [
|
|
47
|
-
- [
|
|
45
|
+
- [useParams](#useparams)
|
|
46
|
+
- [useNavigate](#usenavigate)
|
|
47
|
+
- [useLocation](#uselocation)
|
|
48
|
+
- [useSearchParams](#usesearchparams)
|
|
49
|
+
- [useIsRouting](#useisrouting)
|
|
50
|
+
- [useMatch](#usematch)
|
|
51
|
+
- [useCurrentMatches](#useCurrentMatches)
|
|
52
|
+
- [useBeforeLeave](#usebeforeleave)
|
|
48
53
|
- [SPAs in Deployed Environments](#spas-in-deployed-environments)
|
|
49
54
|
|
|
50
55
|
## Getting Started
|
|
51
56
|
|
|
57
|
+
### Set Up the Router
|
|
58
|
+
|
|
52
59
|
```bash
|
|
53
60
|
# use preferred package manager
|
|
54
61
|
npm add @solidjs/router
|
|
55
62
|
```
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
```tsx
|
|
60
|
-
// app/router.ts
|
|
61
|
-
import { lazy } from "solid-js";
|
|
62
|
-
import { createRouter } from "@solidjs/router";
|
|
64
|
+
Install `@solidjs/router`, then start your application by rendering the router component
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
{ path: "/about", component: lazy(() => import("./pages/About")) },
|
|
68
|
-
{
|
|
69
|
-
path: "/users/:id",
|
|
70
|
-
component: lazy(() => import("./pages/User")),
|
|
71
|
-
children: [
|
|
72
|
-
{ path: "/", component: lazy(() => import("./pages/UserOverview")) },
|
|
73
|
-
{ path: "/settings", component: lazy(() => import("./pages/UserSettings")) }
|
|
74
|
-
]
|
|
75
|
-
},
|
|
76
|
-
{ path: "*404", component: lazy(() => import("./pages/NotFound")) }
|
|
77
|
-
]
|
|
78
|
-
});
|
|
66
|
+
```jsx
|
|
67
|
+
import { render } from "solid-js/web";
|
|
68
|
+
import { Router } from "@solidjs/router";
|
|
79
69
|
|
|
80
|
-
|
|
70
|
+
render(() => <Router />, document.getElementById("app"));
|
|
81
71
|
```
|
|
82
72
|
|
|
83
|
-
This
|
|
73
|
+
This sets up a Router that will match on the url to display the desired page
|
|
84
74
|
|
|
85
|
-
|
|
75
|
+
### Configure Your Routes
|
|
86
76
|
|
|
87
|
-
|
|
88
|
-
// features/admin/routes.ts
|
|
89
|
-
export const adminRoutes = defineRoutes([
|
|
90
|
-
{ path: "/admin", component: Admin, children: [/* ... */] }
|
|
91
|
-
]);
|
|
77
|
+
Solid Router allows you to configure your routes using JSX:
|
|
92
78
|
|
|
93
|
-
|
|
94
|
-
export const Router = createRouter({ routes: [...appRoutes, ...adminRoutes] });
|
|
95
|
-
```
|
|
79
|
+
1. Add each route to a `<Router>` using the `Route` component, specifying a path and a component to render when the user navigates to that path.
|
|
96
80
|
|
|
97
|
-
|
|
81
|
+
```jsx
|
|
82
|
+
import { render } from "solid-js/web";
|
|
83
|
+
import { Router, Route } from "@solidjs/router";
|
|
98
84
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
import { render } from "@solidjs/web";
|
|
102
|
-
import { Router } from "./router";
|
|
85
|
+
import Home from "./pages/Home";
|
|
86
|
+
import Users from "./pages/Users";
|
|
103
87
|
|
|
104
88
|
render(
|
|
105
89
|
() => (
|
|
106
90
|
<Router>
|
|
107
|
-
{
|
|
108
|
-
|
|
109
|
-
<h1>My Site with lots of pages</h1>
|
|
110
|
-
{props.children}
|
|
111
|
-
</>
|
|
112
|
-
)}
|
|
91
|
+
<Route path="/users" component={Users} />
|
|
92
|
+
<Route path="/" component={Home} />
|
|
113
93
|
</Router>
|
|
114
94
|
),
|
|
115
|
-
document.getElementById("app")
|
|
95
|
+
document.getElementById("app")
|
|
116
96
|
);
|
|
117
97
|
```
|
|
118
98
|
|
|
119
|
-
|
|
99
|
+
2. Provide a root level layout
|
|
100
|
+
|
|
101
|
+
This will always be there and won't update on page change. It is the ideal place to put top level navigation and Context Providers
|
|
120
102
|
|
|
121
|
-
```
|
|
122
|
-
import {
|
|
103
|
+
```jsx
|
|
104
|
+
import { render } from "solid-js/web";
|
|
105
|
+
import { Router, Route } from "@solidjs/router";
|
|
123
106
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
107
|
+
import Home from "./pages/Home";
|
|
108
|
+
import Users from "./pages/Users";
|
|
109
|
+
|
|
110
|
+
const App = (props) => (
|
|
111
|
+
<>
|
|
112
|
+
<h1>My Site with lots of pages</h1>
|
|
113
|
+
{props.children}
|
|
114
|
+
</>
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
render(
|
|
118
|
+
() => (
|
|
119
|
+
<Router root={App}>
|
|
120
|
+
<Route path="/users" component={Users} />
|
|
121
|
+
<Route path="/" component={Home} />
|
|
122
|
+
</Router>
|
|
123
|
+
),
|
|
124
|
+
document.getElementById("app")
|
|
125
|
+
);
|
|
129
126
|
```
|
|
130
127
|
|
|
131
|
-
|
|
128
|
+
3. Create a catch-all route (404 page)
|
|
132
129
|
|
|
133
|
-
|
|
130
|
+
We can create catch-all routes for pages not found at any nested level of the router. We use `*` and optionally the name of a parameter to retrieve the rest of the path.
|
|
134
131
|
|
|
135
|
-
|
|
132
|
+
```jsx
|
|
133
|
+
import { render } from "solid-js/web";
|
|
134
|
+
import { Router, Route } from "@solidjs/router";
|
|
136
135
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
| `match(url)` — how would a URL match | `useNavigate`, `usePreloadRoute` — move / warm |
|
|
141
|
-
| `routes`, `config` — what exists | `useIsRouting`, `useRouteMatches`, `useSearchParams` — live state |
|
|
136
|
+
import Home from "./pages/Home";
|
|
137
|
+
import Users from "./pages/Users";
|
|
138
|
+
import NotFound from "./pages/404";
|
|
142
139
|
|
|
143
|
-
|
|
140
|
+
const App = (props) => (
|
|
141
|
+
<>
|
|
142
|
+
<h1>My Site with lots of pages</h1>
|
|
143
|
+
{props.children}
|
|
144
|
+
</>
|
|
145
|
+
);
|
|
144
146
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
render(
|
|
148
|
+
() => (
|
|
149
|
+
<Router root={App}>
|
|
150
|
+
<Route path="/users" component={Users} />
|
|
151
|
+
<Route path="/" component={Home} />
|
|
152
|
+
<Route path="*404" component={NotFound} />
|
|
153
|
+
</Router>
|
|
154
|
+
),
|
|
155
|
+
document.getElementById("app")
|
|
156
|
+
);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
4. Lazy-load route components
|
|
160
|
+
|
|
161
|
+
This way, the `Users` and `Home` components will only be loaded if you're navigating to `/users` or `/`, respectively.
|
|
162
|
+
|
|
163
|
+
```jsx
|
|
164
|
+
import { lazy } from "solid-js";
|
|
165
|
+
import { render } from "solid-js/web";
|
|
166
|
+
import { Router, Route } from "@solidjs/router";
|
|
167
|
+
|
|
168
|
+
const Users = lazy(() => import("./pages/Users"));
|
|
169
|
+
const Home = lazy(() => import("./pages/Home"));
|
|
170
|
+
|
|
171
|
+
const App = (props) => (
|
|
172
|
+
<>
|
|
173
|
+
<h1>My Site with lots of pages</h1>
|
|
174
|
+
{props.children}
|
|
175
|
+
</>
|
|
176
|
+
);
|
|
148
177
|
|
|
149
|
-
|
|
178
|
+
render(
|
|
179
|
+
() => (
|
|
180
|
+
<Router root={App}>
|
|
181
|
+
<Route path="/users" component={Users} />
|
|
182
|
+
<Route path="/" component={Home} />
|
|
183
|
+
</Router>
|
|
184
|
+
),
|
|
185
|
+
document.getElementById("app")
|
|
186
|
+
);
|
|
150
187
|
```
|
|
151
188
|
|
|
152
|
-
|
|
189
|
+
### Create Links to Your Routes
|
|
153
190
|
|
|
154
|
-
|
|
191
|
+
Use an anchor tag that takes you to a route:
|
|
155
192
|
|
|
156
|
-
|
|
193
|
+
```jsx
|
|
194
|
+
import { lazy } from "solid-js";
|
|
195
|
+
import { render } from "solid-js/web";
|
|
196
|
+
import { Router, Route } from "@solidjs/router";
|
|
197
|
+
|
|
198
|
+
const Users = lazy(() => import("./pages/Users"));
|
|
199
|
+
const Home = lazy(() => import("./pages/Home"));
|
|
200
|
+
|
|
201
|
+
const App = (props) => (
|
|
202
|
+
<>
|
|
203
|
+
<nav>
|
|
204
|
+
<a href="/about">About</a>
|
|
205
|
+
<a href="/">Home</a>
|
|
206
|
+
</nav>
|
|
207
|
+
<h1>My Site with lots of pages</h1>
|
|
208
|
+
{props.children}
|
|
209
|
+
</>
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
render(
|
|
213
|
+
() => (
|
|
214
|
+
<Router root={App}>
|
|
215
|
+
<Route path="/users" component={Users} />
|
|
216
|
+
<Route path="/" component={Home} />
|
|
217
|
+
</Router>
|
|
218
|
+
),
|
|
219
|
+
document.getElementById("app")
|
|
220
|
+
);
|
|
221
|
+
```
|
|
157
222
|
|
|
158
|
-
|
|
159
|
-
| -------------- | --------------------------------------- | ------------------------------------------------------------------ |
|
|
160
|
-
| `path` | `string \| string[]` | Path partial for this route segment |
|
|
161
|
-
| `component` | `Component` | Component rendered for the matched segment |
|
|
162
|
-
| `children` | `RouteDefinition \| RouteDefinition[] \| () => Promise<...>` | Nested route definitions, or a thunk for a [lazy subtree](#lazy-route-subtrees) |
|
|
163
|
-
| `preload` | `RoutePreloadFunc` | Called on preload intent (hover/focus) and navigation |
|
|
164
|
-
| `matchFilters` | `MatchFilters` | Additional constraints for matching parameters |
|
|
165
|
-
| `search` | `StandardSchemaV1` | Search-param validator; its types flow into `paths` and hooks |
|
|
166
|
-
| `info` | `Record<string, any>` | Arbitrary metadata, readable via `useRouteMatches` |
|
|
223
|
+
## Dynamic Routes
|
|
167
224
|
|
|
168
|
-
|
|
225
|
+
If you don't know the path ahead of time, you might want to treat part of the path as a flexible parameter that is passed on to the component.
|
|
169
226
|
|
|
170
|
-
|
|
227
|
+
```jsx
|
|
228
|
+
import { lazy } from "solid-js";
|
|
229
|
+
import { render } from "solid-js/web";
|
|
230
|
+
import { Router, Route } from "@solidjs/router";
|
|
171
231
|
|
|
172
|
-
|
|
232
|
+
const Users = lazy(() => import("./pages/Users"));
|
|
233
|
+
const User = lazy(() => import("./pages/User"));
|
|
234
|
+
const Home = lazy(() => import("./pages/Home"));
|
|
173
235
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
236
|
+
render(
|
|
237
|
+
() => (
|
|
238
|
+
<Router>
|
|
239
|
+
<Route path="/users" component={Users} />
|
|
240
|
+
<Route path="/users/:id" component={User} />
|
|
241
|
+
<Route path="/" component={Home} />
|
|
242
|
+
</Router>
|
|
243
|
+
),
|
|
244
|
+
document.getElementById("app")
|
|
245
|
+
);
|
|
179
246
|
```
|
|
180
247
|
|
|
181
|
-
|
|
248
|
+
The colon indicates that `id` can be any string, and as long as the URL fits that pattern, the `User` component will show.
|
|
249
|
+
|
|
250
|
+
You can then access that `id` from within a route component with `useParams`.
|
|
182
251
|
|
|
183
|
-
**Note on Animation/Transitions**:
|
|
252
|
+
**Note on Animation/Transitions**:
|
|
253
|
+
Routes that share the same path match will be treated as the same route. If you want to force re-render you can wrap your component in a keyed `<Show>` like:
|
|
184
254
|
|
|
185
|
-
```
|
|
255
|
+
```jsx
|
|
186
256
|
<Show when={params.something} keyed>
|
|
187
257
|
<MyComponent />
|
|
188
258
|
</Show>
|
|
189
259
|
```
|
|
190
260
|
|
|
191
|
-
|
|
261
|
+
---
|
|
192
262
|
|
|
193
|
-
Each parameter can be validated
|
|
263
|
+
Each path parameter can be validated using a `MatchFilter`.
|
|
264
|
+
This allows for more complex routing descriptions than just checking the presence of a parameter.
|
|
194
265
|
|
|
195
|
-
```
|
|
196
|
-
import {
|
|
266
|
+
```jsx
|
|
267
|
+
import { lazy } from "solid-js";
|
|
268
|
+
import { render } from "solid-js/web";
|
|
269
|
+
import { Router, Route } from "@solidjs/router";
|
|
270
|
+
import type { MatchFilters } from "@solidjs/router";
|
|
271
|
+
|
|
272
|
+
const User = lazy(() => import("./pages/User"));
|
|
197
273
|
|
|
198
274
|
const filters: MatchFilters = {
|
|
199
|
-
parent: ["mom", "dad"],
|
|
200
|
-
id: /^\d+$/,
|
|
201
|
-
withHtmlExtension: (v: string) => v.length > 5 && v.endsWith(".html")
|
|
275
|
+
parent: ["mom", "dad"], // allow enum values
|
|
276
|
+
id: /^\d+$/, // only allow numbers
|
|
277
|
+
withHtmlExtension: (v: string) => v.length > 5 && v.endsWith(".html"), // we want an `*.html` extension
|
|
202
278
|
};
|
|
203
279
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
280
|
+
render(
|
|
281
|
+
() => (
|
|
282
|
+
<Router>
|
|
283
|
+
<Route
|
|
284
|
+
path="/users/:parent/:id/:withHtmlExtension"
|
|
285
|
+
component={User}
|
|
286
|
+
matchFilters={filters}
|
|
287
|
+
/>
|
|
288
|
+
</Router>
|
|
289
|
+
),
|
|
290
|
+
document.getElementById("app")
|
|
291
|
+
);
|
|
207
292
|
```
|
|
208
293
|
|
|
209
|
-
|
|
294
|
+
Here, we have added the `matchFilters` prop. This allows us to validate the `parent`, `id` and `withHtmlExtension` parameters against the filters defined in `filters`.
|
|
295
|
+
If the validation fails, the route will not match.
|
|
210
296
|
|
|
211
|
-
|
|
297
|
+
So in this example:
|
|
212
298
|
|
|
213
|
-
|
|
214
|
-
|
|
299
|
+
- `/users/mom/123/contact.html` would match,
|
|
300
|
+
- `/users/dad/123/about.html` would match,
|
|
301
|
+
- `/users/aunt/123/contact.html` would not match as `:parent` is not 'mom' or 'dad',
|
|
302
|
+
- `/users/mom/me/contact.html` would not match as `:id` is not a number,
|
|
303
|
+
- `/users/dad/123/contact` would not match as `:withHtmlExtension` is missing `.html`.
|
|
215
304
|
|
|
216
|
-
|
|
217
|
-
paths.users("abc"); // type error
|
|
218
|
-
```
|
|
305
|
+
---
|
|
219
306
|
|
|
220
307
|
### Optional Parameters
|
|
221
308
|
|
|
222
|
-
|
|
309
|
+
Parameters can be specified as optional by adding a question mark to the end of the parameter name:
|
|
223
310
|
|
|
224
|
-
```
|
|
311
|
+
```jsx
|
|
225
312
|
// Matches stories and stories/123 but not stories/123/comments
|
|
226
|
-
|
|
313
|
+
<Route path="/stories/:id?" component={Stories} />
|
|
227
314
|
```
|
|
228
315
|
|
|
229
316
|
### Wildcard Routes
|
|
230
317
|
|
|
231
|
-
|
|
318
|
+
`:param` lets you match an arbitrary name at that point in the path. You can use `*` to match any end of the path:
|
|
319
|
+
|
|
320
|
+
```jsx
|
|
321
|
+
// Matches any path that begins with foo, including foo/, foo/a/, foo/a/b/c
|
|
322
|
+
<Route path="foo/*" component={Foo} />
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
If you want to expose the wild part of the path to the component as a parameter, you can name it:
|
|
232
326
|
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
{ path: "foo/*any", component: Foo } // rest of the path available as params.any
|
|
327
|
+
```jsx
|
|
328
|
+
<Route path="foo/*any" component={Foo} />
|
|
236
329
|
```
|
|
237
330
|
|
|
238
|
-
|
|
331
|
+
Note that the wildcard token must be the last part of the path; `foo/*any/bar` won't create any routes.
|
|
239
332
|
|
|
240
333
|
### Multiple Paths
|
|
241
334
|
|
|
242
|
-
|
|
335
|
+
Routes also support defining multiple paths using an array. This allows a route to remain mounted and not rerender when switching between two or more locations that it matches:
|
|
243
336
|
|
|
244
|
-
```
|
|
245
|
-
// Navigating from login to register does not re-render
|
|
246
|
-
|
|
337
|
+
```jsx
|
|
338
|
+
// Navigating from login to register does not cause the Login component to re-render
|
|
339
|
+
<Route path={["login", "register"]} component={Login} />
|
|
247
340
|
```
|
|
248
341
|
|
|
249
|
-
|
|
342
|
+
## Nested Routes
|
|
343
|
+
|
|
344
|
+
The following two route definitions have the same result:
|
|
345
|
+
|
|
346
|
+
```jsx
|
|
347
|
+
<Route path="/users/:id" component={User} />
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
```jsx
|
|
351
|
+
<Route path="/users">
|
|
352
|
+
<Route path="/:id" component={User} />
|
|
353
|
+
</Route>
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`/users/:id` renders the `<User/>` component, and `/users/` is an empty route.
|
|
357
|
+
|
|
358
|
+
Only leaf Route nodes (innermost `Route` components) are given a route. If you want to make the parent its own route, you have to specify it separately:
|
|
359
|
+
|
|
360
|
+
```jsx
|
|
361
|
+
//This won't work the way you'd expect
|
|
362
|
+
<Route path="/users" component={Users}>
|
|
363
|
+
<Route path="/:id" component={User} />
|
|
364
|
+
</Route>
|
|
365
|
+
|
|
366
|
+
// This works
|
|
367
|
+
<Route path="/users" component={Users} />
|
|
368
|
+
<Route path="/users/:id" component={User} />
|
|
369
|
+
|
|
370
|
+
// This also works
|
|
371
|
+
<Route path="/users">
|
|
372
|
+
<Route path="/" component={Users} />
|
|
373
|
+
<Route path="/:id" component={User} />
|
|
374
|
+
</Route>
|
|
375
|
+
```
|
|
250
376
|
|
|
251
|
-
|
|
377
|
+
You can also take advantage of nesting by using `props.children` passed to the route component.
|
|
252
378
|
|
|
253
|
-
```
|
|
379
|
+
```jsx
|
|
254
380
|
function PageWrapper(props) {
|
|
255
381
|
return (
|
|
256
382
|
<div>
|
|
257
|
-
<h1>We love our users
|
|
383
|
+
<h1> We love our users! </h1>
|
|
258
384
|
{props.children}
|
|
259
|
-
<
|
|
385
|
+
<A href="/">Back Home</A>
|
|
260
386
|
</div>
|
|
261
387
|
);
|
|
262
388
|
}
|
|
263
389
|
|
|
264
|
-
|
|
265
|
-
{
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
children: [
|
|
269
|
-
{ path: "/", component: Users },
|
|
270
|
-
{ path: "/:id", component: User }
|
|
271
|
-
]
|
|
272
|
-
}
|
|
273
|
-
]);
|
|
390
|
+
<Route path="/users" component={PageWrapper}>
|
|
391
|
+
<Route path="/" component={Users} />
|
|
392
|
+
<Route path="/:id" component={User} />
|
|
393
|
+
</Route>;
|
|
274
394
|
```
|
|
275
395
|
|
|
276
|
-
|
|
396
|
+
The routes are still configured the same, but now the route elements will appear inside the parent element where the `props.children` was declared.
|
|
397
|
+
|
|
398
|
+
You can nest indefinitely - just remember that only leaf nodes will become their own routes. In this example, the only route created is `/layer1/layer2`, and it appears as three nested divs.
|
|
399
|
+
|
|
400
|
+
```jsx
|
|
401
|
+
<Route
|
|
402
|
+
path="/"
|
|
403
|
+
component={(props) => <div>Onion starts here {props.children}</div>}
|
|
404
|
+
>
|
|
405
|
+
<Route
|
|
406
|
+
path="layer1"
|
|
407
|
+
component={(props) => <div>Another layer {props.children}</div>}
|
|
408
|
+
>
|
|
409
|
+
<Route path="layer2" component={() => <div>Innermost layer</div>} />
|
|
410
|
+
</Route>
|
|
411
|
+
</Route>
|
|
412
|
+
```
|
|
277
413
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
414
|
+
## Preload Functions
|
|
415
|
+
|
|
416
|
+
Even with smart caches it is possible that we have waterfalls both with view logic and with lazy loaded code. With preload functions, we can instead start fetching the data parallel to loading the route, so we can use the data as soon as possible. The preload function is called when the Route is loaded or eagerly when links are hovered.
|
|
417
|
+
|
|
418
|
+
As its only argument, the preload function is passed an object that you can use to access route information:
|
|
419
|
+
|
|
420
|
+
```js
|
|
421
|
+
import { lazy } from "solid-js";
|
|
422
|
+
import { Route } from "@solidjs/router";
|
|
423
|
+
|
|
424
|
+
const User = lazy(() => import("./pages/users/[id].js"));
|
|
425
|
+
|
|
426
|
+
// preload function
|
|
427
|
+
function preloadUser({ params, location }) {
|
|
428
|
+
// do preloading
|
|
287
429
|
}
|
|
430
|
+
|
|
431
|
+
// Pass it in the route definition
|
|
432
|
+
<Route path="/users/:id" component={User} preload={preloadUser} />;
|
|
288
433
|
```
|
|
289
434
|
|
|
290
|
-
|
|
435
|
+
| key | type | description |
|
|
436
|
+
| -------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
437
|
+
| params | object | The route parameters (same value as calling `useParams()` inside the route component) |
|
|
438
|
+
| location | `{ pathname, search, hash, query, state, key}` | An object that you can use to get more information about the path (corresponds to [`useLocation()`](#uselocation)) |
|
|
439
|
+
| intent | `"initial", "navigate", "native", "preload"` | Indicates why this function is being called. <ul><li>"initial" - the route is being initially shown (ie page load)</li><li>"native" - navigate originated from the browser (eg back/forward)</li><li>"navigate" - navigate originated from the router (eg call to navigate or anchor clicked)</li><li>"preload" - not navigating, just preloading (eg link hover)</li></ul> |
|
|
291
440
|
|
|
292
|
-
|
|
441
|
+
A common pattern is to export the preload function and data wrappers that corresponds to a route in a dedicated `route.data.js` file. This way, the data function can be imported without loading anything else.
|
|
293
442
|
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
]);
|
|
443
|
+
```js
|
|
444
|
+
import { lazy } from "solid-js";
|
|
445
|
+
import { Route } from "@solidjs/router";
|
|
446
|
+
import preloadUser from "./pages/users/[id].data.js";
|
|
447
|
+
const User = lazy(() => import("/pages/users/[id].js"));
|
|
300
448
|
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
routes: [
|
|
304
|
-
{ path: "/", component: Home },
|
|
305
|
-
{ path: "/admin", component: AdminShell, children: () => import("./admin/routes") }
|
|
306
|
-
]
|
|
307
|
-
});
|
|
449
|
+
// In the Route definition
|
|
450
|
+
<Route path="/users/:id" component={User} preload={preloadUser} />;
|
|
308
451
|
```
|
|
309
452
|
|
|
310
|
-
The
|
|
453
|
+
The `preload` function's return value is passed to the page component for any intent other than `"preload"`, allowing you to initialize data or alternatively use our new Data APIs:
|
|
311
454
|
|
|
312
|
-
|
|
313
|
-
- **Navigation**: the table load folds into the navigation transition — the old screen holds until the subtree (and its matched components) are ready, exactly like a `lazy()` route component.
|
|
314
|
-
- **Preloading**: hover intent kicks the table load, and when it lands the preload continues into the inner routes' components and `preload` functions — one cascading warm-up from the earliest possible moment.
|
|
315
|
-
- **Server**: SSR resolves matched boundaries during the render (use the streaming entry points — `renderToStream`/`renderToStringAsync` — as with any async work), and the single-flight collector resolves them before its data pass.
|
|
455
|
+
## Data APIs
|
|
316
456
|
|
|
317
|
-
|
|
457
|
+
Keep in mind that these are entirely optional, but they demonstrate the power of our preload mechanism.
|
|
318
458
|
|
|
319
|
-
|
|
459
|
+
### `query`
|
|
320
460
|
|
|
321
|
-
|
|
461
|
+
To prevent duplicate fetching and to handle refetching triggers, we provide a query API that accepts a function and returns the same function.
|
|
322
462
|
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
paths.about() // zero-arg/search calls terminate to a plain string
|
|
328
|
-
paths() // "/" — the root
|
|
463
|
+
```jsx
|
|
464
|
+
const getUser = query(async (id) => {
|
|
465
|
+
return (await fetch(`/api/users/${id}`)).json();
|
|
466
|
+
}, "users"); // used as the query key + serialized arguments
|
|
329
467
|
```
|
|
330
468
|
|
|
331
|
-
|
|
469
|
+
It is expected that the arguments to the query function are serializable.
|
|
332
470
|
|
|
333
|
-
|
|
471
|
+
This query accomplishes the following:
|
|
334
472
|
|
|
335
|
-
|
|
473
|
+
1. It does deduping on the server for the lifetime of the request.
|
|
474
|
+
2. It fills a preload cache in the browser which lasts 5 seconds. When a route is preloaded on hover or when preload is called when entering a route it will make sure to dedupe calls.
|
|
475
|
+
3. We have a reactive refetch mechanism based on key. So we can tell routes that aren't new to retrigger on action revalidation.
|
|
476
|
+
4. It will serve as a back/forward cache for browser navigation up to 5 mins. Any user based navigation or link click bypasses this cache. Revalidation or new fetch updates the cache.
|
|
336
477
|
|
|
337
|
-
|
|
478
|
+
Using it with preload function might look like:
|
|
338
479
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
| `state` | JSON string [pushed](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) onto the history stack |
|
|
344
|
-
| `preload` | Set to `"false"` to opt this link out of hover/focus preloading |
|
|
345
|
-
| `link` | Marks a router link when `explicitLinks` is enabled |
|
|
346
|
-
| `target` | Any value (e.g. `_self`) opts the anchor out of router handling |
|
|
480
|
+
```js
|
|
481
|
+
import { lazy } from "solid-js";
|
|
482
|
+
import { Route } from "@solidjs/router";
|
|
483
|
+
import { getUser } from ... // the query function
|
|
347
484
|
|
|
348
|
-
|
|
349
|
-
<a href={paths.login} replace>Log in</a>
|
|
350
|
-
<a href={paths.docs} noscroll>Docs</a>
|
|
351
|
-
<a href="https://example.com">External — untouched</a>
|
|
352
|
-
```
|
|
485
|
+
const User = lazy(() => import("./pages/users/[id].js"));
|
|
353
486
|
|
|
354
|
-
|
|
487
|
+
// preload function
|
|
488
|
+
function preloadUser({params, location}) {
|
|
489
|
+
void getUser(params.id)
|
|
490
|
+
}
|
|
355
491
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
nav a[data-active] { color: var(--accent); } /* exact or prefix match */
|
|
359
|
-
a[data-pending] { opacity: 0.6; } /* target of in-flight navigation */
|
|
492
|
+
// Pass it in the route definition
|
|
493
|
+
<Route path="/users/:id" component={User} preload={preloadUser} />;
|
|
360
494
|
```
|
|
361
495
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
For component-library links that need reactive state beyond CSS, `useLinkState` is the programmatic counterpart of the attribute vocabulary:
|
|
496
|
+
Inside your page component you:
|
|
365
497
|
|
|
366
|
-
```
|
|
367
|
-
|
|
498
|
+
```jsx
|
|
499
|
+
// pages/users/[id].js
|
|
500
|
+
import { getUser } from ... // the query function
|
|
368
501
|
|
|
369
|
-
function
|
|
370
|
-
const
|
|
371
|
-
return (
|
|
372
|
-
<a href={props.href} class="tab" data-selected={link.active() || undefined}>
|
|
373
|
-
{props.children}
|
|
374
|
-
</a>
|
|
375
|
-
);
|
|
502
|
+
export default function User(props) {
|
|
503
|
+
const user = createAsync(() => getUser(props.params.id));
|
|
504
|
+
return <h1>{user().name}</h1>;
|
|
376
505
|
}
|
|
377
506
|
```
|
|
378
507
|
|
|
379
|
-
|
|
508
|
+
Cached function has a few useful methods for getting the key that are useful for invalidation.
|
|
380
509
|
|
|
381
|
-
|
|
510
|
+
```ts
|
|
511
|
+
let id = 5;
|
|
382
512
|
|
|
383
|
-
|
|
384
|
-
|
|
513
|
+
getUser.key; // returns "users"
|
|
514
|
+
getUser.keyFor(id); // returns "users[5]"
|
|
515
|
+
```
|
|
385
516
|
|
|
386
|
-
|
|
517
|
+
You can revalidate the query using the `revalidate` method or you can set `revalidate` keys on your response from your actions. If you pass the whole key it will invalidate all the entries for the query (ie "users" in the example above). You can also invalidate a single entry by using `keyFor`.
|
|
387
518
|
|
|
388
|
-
|
|
389
|
-
void getUser(params.id);
|
|
390
|
-
}
|
|
519
|
+
`query` can be defined anywhere and then used inside your components with:
|
|
391
520
|
|
|
392
|
-
|
|
393
|
-
```
|
|
521
|
+
### `createAsync`
|
|
394
522
|
|
|
395
|
-
|
|
523
|
+
This is light wrapper over `createResource` that aims to serve as stand-in for a future primitive we intend to bring to Solid core in 2.0. It is a simpler async primitive where the function tracks like `createMemo` and it expects a promise back that it turns into a Signal. Reading it before it is ready causes Suspense/Transitions to trigger.
|
|
396
524
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
| location | `{ pathname, search, hash, query, state, key }` | Path information (corresponds to [`useLocation()`](#uselocation)) |
|
|
401
|
-
| intent | `"initial" \| "navigate" \| "native" \| "preload"` | Why this is being called: `initial` — first render; `navigate` — router navigation; `native` — browser back/forward; `preload` — link hover/focus, not navigating |
|
|
525
|
+
```jsx
|
|
526
|
+
const user = createAsync((currentValue) => getUser(params.id));
|
|
527
|
+
```
|
|
402
528
|
|
|
403
|
-
|
|
529
|
+
It also preserves `latest` field from `createResource`. Note that it will be removed in the future.
|
|
404
530
|
|
|
405
|
-
|
|
531
|
+
```jsx
|
|
532
|
+
const user = createAsync((currentValue) => getUser(params.id));
|
|
533
|
+
return <h1>{user.latest.name}</h1>;
|
|
534
|
+
```
|
|
406
535
|
|
|
407
|
-
|
|
536
|
+
Using `query` in `createResource` directly won't work properly as the fetcher is not reactive and it won't invalidate properly.
|
|
408
537
|
|
|
409
|
-
### `
|
|
538
|
+
### `createAsyncStore`
|
|
410
539
|
|
|
411
|
-
|
|
540
|
+
Similar to `createAsync` except it uses a deeply reactive store. Perfect for applying fine-grained changes to large model data that updates.
|
|
541
|
+
It also supports `latest` field which will be removed in the future.
|
|
412
542
|
|
|
413
|
-
```
|
|
414
|
-
const
|
|
415
|
-
return (await fetch(`/api/users/${id}`)).json();
|
|
416
|
-
}, "users"); // query key; arguments are serialized alongside it
|
|
543
|
+
```jsx
|
|
544
|
+
const todos = createAsyncStore(() => getTodos());
|
|
417
545
|
```
|
|
418
546
|
|
|
419
|
-
|
|
547
|
+
### `action`
|
|
548
|
+
|
|
549
|
+
Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response helpers can be found below.
|
|
420
550
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
3. Refetches reactively by key on action revalidation.
|
|
424
|
-
4. Serves as a back/forward cache for browser navigation up to 5 minutes; user-initiated navigation bypasses it.
|
|
551
|
+
```jsx
|
|
552
|
+
import { action, revalidate, redirect } from "@solidjs/router"
|
|
425
553
|
|
|
426
|
-
|
|
554
|
+
// anywhere
|
|
555
|
+
const myAction = action(async (data) => {
|
|
556
|
+
await doMutation(data);
|
|
557
|
+
throw redirect("/", { revalidate: getUser.keyFor(data.id) }); // throw a response to do a redirect
|
|
558
|
+
});
|
|
427
559
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
return <h1>{user().name}</h1>;
|
|
560
|
+
// in component
|
|
561
|
+
<form action={myAction} method="post" />
|
|
431
562
|
|
|
432
|
-
//
|
|
433
|
-
|
|
563
|
+
//or
|
|
564
|
+
<button type="submit" formaction={myAction}></button>
|
|
434
565
|
```
|
|
435
566
|
|
|
436
|
-
|
|
567
|
+
Actions only work with post requests, so make sure to put `method="post"` on your form.
|
|
437
568
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
569
|
+
Sometimes it might be easier to deal with typed data instead of `FormData` and adding additional hidden fields. For that reason Actions have a with method. That works similar to `bind` which applies the arguments in order.
|
|
570
|
+
|
|
571
|
+
Picture an action that deletes Todo Item:
|
|
572
|
+
|
|
573
|
+
```js
|
|
574
|
+
const deleteTodo = action(async (formData: FormData) => {
|
|
575
|
+
const id = Number(formData.get("id"))
|
|
576
|
+
await api.deleteTodo(id)
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
<form action={deleteTodo} method="post">
|
|
580
|
+
<input type="hidden" name="id" value={todo.id} />
|
|
581
|
+
<button type="submit">Delete</button>
|
|
582
|
+
</form>
|
|
441
583
|
```
|
|
442
584
|
|
|
443
|
-
|
|
585
|
+
Instead with `with` you can write this:
|
|
444
586
|
|
|
445
|
-
|
|
587
|
+
```js
|
|
588
|
+
const deleteTodo = action(api.deleteTodo)
|
|
446
589
|
|
|
447
|
-
|
|
590
|
+
<form action={deleteTodo.with(todo.id)} method="post">
|
|
591
|
+
<button type="submit">Delete</button>
|
|
592
|
+
</form>
|
|
593
|
+
```
|
|
448
594
|
|
|
449
|
-
|
|
450
|
-
import { action } from "@solidjs/router";
|
|
451
|
-
import { redirect } from "@solidjs/web";
|
|
452
|
-
import { paths } from "./router";
|
|
595
|
+
Actions also take a second argument which can be the name or an option object with `name` and `onComplete`. `name` is used to identify SSR actions that aren't server functions (see note below). `onComplete` allows you to configure behavior when `action`s complete. Keep in mind `onComplete` does not work when JavaScript is disabled.
|
|
453
596
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
597
|
+
#### Notes on `<form>` implementation and SSR
|
|
598
|
+
|
|
599
|
+
This requires stable references as you can only serialize a string as an attribute, and across SSR they'd need to match. The solution is providing a unique name.
|
|
600
|
+
|
|
601
|
+
```jsx
|
|
602
|
+
const myAction = action(async (args) => {}, "my-action");
|
|
458
603
|
```
|
|
459
604
|
|
|
460
|
-
|
|
461
|
-
<form action={updateUser} method="post">
|
|
462
|
-
<button>Save</button>
|
|
463
|
-
</form>
|
|
605
|
+
### `useAction`
|
|
464
606
|
|
|
465
|
-
|
|
466
|
-
|
|
607
|
+
Instead of forms you can use actions directly by wrapping them in a `useAction` primitive. This is how we get the router context.
|
|
608
|
+
|
|
609
|
+
```jsx
|
|
610
|
+
// in component
|
|
611
|
+
const submit = useAction(myAction);
|
|
612
|
+
submit(...args);
|
|
467
613
|
```
|
|
468
614
|
|
|
469
|
-
|
|
615
|
+
The outside of a form context you can use custom data instead of formData, and these helpers preserve types. However, even when used with server functions (in projects like SolidStart) this requires client side javascript and is not Progressive Enhanceable like forms are.
|
|
616
|
+
|
|
617
|
+
### `useSubmission`/`useSubmissions`
|
|
470
618
|
|
|
471
|
-
|
|
472
|
-
|
|
619
|
+
Are used to injecting the optimistic updates while actions are in flight. They either return a single Submission(latest) or all that match with an optional filter function.
|
|
620
|
+
|
|
621
|
+
```jsx
|
|
622
|
+
type Submission<T, U> = {
|
|
623
|
+
readonly input: T;
|
|
624
|
+
readonly result?: U;
|
|
625
|
+
readonly pending: boolean;
|
|
626
|
+
readonly url: string;
|
|
627
|
+
clear: () => void;
|
|
628
|
+
retry: () => void;
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
const submissions = useSubmissions(action, (input) => filter(input));
|
|
632
|
+
const submission = useSubmission(action, (input) => filter(input));
|
|
473
633
|
```
|
|
474
634
|
|
|
475
|
-
|
|
635
|
+
### Response Helpers
|
|
636
|
+
|
|
637
|
+
These are used to communicate router navigations from query/actions, and can include invalidation hints. Generally these are thrown to not interfere the with the types and make it clear that function ends execution at that point.
|
|
476
638
|
|
|
477
|
-
|
|
639
|
+
#### `redirect(path, options)`
|
|
640
|
+
|
|
641
|
+
Redirects to the next route
|
|
642
|
+
|
|
643
|
+
```js
|
|
644
|
+
const getUser = query(() => {
|
|
645
|
+
const user = await api.getCurrentUser()
|
|
646
|
+
if (!user) throw redirect("/login");
|
|
647
|
+
return user;
|
|
648
|
+
})
|
|
649
|
+
```
|
|
478
650
|
|
|
479
|
-
|
|
651
|
+
#### `reload(options)`
|
|
480
652
|
|
|
481
|
-
|
|
482
|
-
import { createOptimisticStore } from "solid-js";
|
|
483
|
-
import { action, query } from "@solidjs/router";
|
|
653
|
+
Reloads the data on the current page
|
|
484
654
|
|
|
485
|
-
|
|
486
|
-
const
|
|
655
|
+
```js
|
|
656
|
+
const getTodo = query(async (id: number) => {
|
|
657
|
+
const todo = await fetchTodo(id);
|
|
658
|
+
return todo;
|
|
659
|
+
}, "todo");
|
|
487
660
|
|
|
488
|
-
const
|
|
489
|
-
await
|
|
490
|
-
|
|
491
|
-
}, "add-todo").onSubmit(todo => {
|
|
492
|
-
setTodos(items => {
|
|
493
|
-
items.push({ ...todo, pending: true });
|
|
494
|
-
});
|
|
661
|
+
const updateTodo = action(async (todo: Todo) => {
|
|
662
|
+
await updateTodo(todo.id, todo);
|
|
663
|
+
reload({ revalidate: getTodo.keyFor(todo.id) });
|
|
495
664
|
});
|
|
496
665
|
```
|
|
497
666
|
|
|
498
|
-
|
|
667
|
+
## Config Based Routing
|
|
499
668
|
|
|
500
|
-
|
|
669
|
+
You don't have to use JSX to set up your routes; you can pass an array of route definitions:
|
|
501
670
|
|
|
502
|
-
|
|
671
|
+
```jsx
|
|
672
|
+
import { lazy } from "solid-js";
|
|
673
|
+
import { render } from "solid-js/web";
|
|
674
|
+
import { Router } from "@solidjs/router";
|
|
503
675
|
|
|
504
|
-
|
|
505
|
-
|
|
676
|
+
const routes = [
|
|
677
|
+
{
|
|
678
|
+
path: "/users",
|
|
679
|
+
component: lazy(() => import("/pages/users.js")),
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
path: "/users/:id",
|
|
683
|
+
component: lazy(() => import("/pages/users/[id].js")),
|
|
684
|
+
children: [
|
|
685
|
+
{
|
|
686
|
+
path: "/",
|
|
687
|
+
component: lazy(() => import("/pages/users/[id]/index.js")),
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
path: "/settings",
|
|
691
|
+
component: lazy(() => import("/pages/users/[id]/settings.js")),
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
path: "/*all",
|
|
695
|
+
component: lazy(() => import("/pages/users/[id]/[...all].js")),
|
|
696
|
+
},
|
|
697
|
+
],
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
path: "/",
|
|
701
|
+
component: lazy(() => import("/pages/index.js")),
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
path: "/*all",
|
|
705
|
+
component: lazy(() => import("/pages/[...all].js")),
|
|
706
|
+
},
|
|
707
|
+
];
|
|
506
708
|
|
|
507
|
-
<
|
|
508
|
-
<button type="submit">Delete</button>
|
|
509
|
-
</form>;
|
|
709
|
+
render(() => <Router>{routes}</Router>, document.getElementById("app"));
|
|
510
710
|
```
|
|
511
711
|
|
|
512
|
-
|
|
712
|
+
Also you can pass a single route definition object for a single route:
|
|
513
713
|
|
|
514
|
-
|
|
714
|
+
```jsx
|
|
715
|
+
import { lazy } from "solid-js";
|
|
716
|
+
import { render } from "solid-js/web";
|
|
717
|
+
import { Router } from "@solidjs/router";
|
|
515
718
|
|
|
516
|
-
|
|
719
|
+
const route = {
|
|
720
|
+
path: "/",
|
|
721
|
+
component: lazy(() => import("/pages/index.js")),
|
|
722
|
+
};
|
|
517
723
|
|
|
518
|
-
|
|
519
|
-
const submit = useAction(myAction);
|
|
520
|
-
submit(...args);
|
|
724
|
+
render(() => <Router>{route}</Router>, document.getElementById("app"));
|
|
521
725
|
```
|
|
522
726
|
|
|
523
|
-
|
|
727
|
+
## Alternative Routers
|
|
524
728
|
|
|
525
|
-
|
|
729
|
+
### Hash Mode Router
|
|
526
730
|
|
|
527
|
-
|
|
528
|
-
const submissions = useSubmissions(action, input => filter(input));
|
|
529
|
-
const latest = submissions.at(-1);
|
|
530
|
-
// { input, result?, error, url, clear(), retry() }
|
|
531
|
-
```
|
|
731
|
+
By default, Solid Router uses `location.pathname` as route path. You can simply switch to hash mode through using `<HashRouter>`.
|
|
532
732
|
|
|
533
|
-
|
|
733
|
+
```jsx
|
|
734
|
+
import { HashRouter } from "@solidjs/router";
|
|
534
735
|
|
|
535
|
-
|
|
736
|
+
<HashRouter />;
|
|
737
|
+
```
|
|
536
738
|
|
|
537
|
-
|
|
739
|
+
### Memory Mode Router
|
|
538
740
|
|
|
539
|
-
|
|
540
|
-
import * as v from "valibot";
|
|
741
|
+
You can also use memory mode router for testing purpose.
|
|
541
742
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
search: v.object({
|
|
547
|
-
q: v.optional(v.string(), ""),
|
|
548
|
-
page: v.optional(v.pipe(v.unknown(), v.transform(Number)), 1)
|
|
549
|
-
})
|
|
550
|
-
}
|
|
551
|
-
]);
|
|
743
|
+
```jsx
|
|
744
|
+
import { MemoryRouter } from "@solidjs/router";
|
|
745
|
+
|
|
746
|
+
<MemoryRouter />;
|
|
552
747
|
```
|
|
553
748
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
setSearch({ page: search.page + 1 }); // typed setter
|
|
749
|
+
### SSR Routing
|
|
750
|
+
|
|
751
|
+
For SSR you can use the static router directly or the browser Router defaults to it on the server, just pass in the url.
|
|
558
752
|
|
|
559
|
-
|
|
753
|
+
```jsx
|
|
754
|
+
import { isServer } from "solid-js/web";
|
|
755
|
+
import { Router } from "@solidjs/router";
|
|
756
|
+
|
|
757
|
+
<Router url={isServer ? req.url : ""} />;
|
|
560
758
|
```
|
|
561
759
|
|
|
562
|
-
|
|
760
|
+
## Components
|
|
761
|
+
|
|
762
|
+
### `<Router>`
|
|
763
|
+
|
|
764
|
+
This is the main Router component for the browser.
|
|
765
|
+
|
|
766
|
+
| prop | type | description |
|
|
767
|
+
| ------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
768
|
+
| children | `JSX.Element`, `RouteDefinition`, or `RouteDefinition[]` | The route definitions |
|
|
769
|
+
| root | Component | Top level layout component |
|
|
770
|
+
| base | string | Base url to use for matching routes |
|
|
771
|
+
| actionBase | string | Root url for server actions, default: `/_server` |
|
|
772
|
+
| preload | boolean | Enables/disables preloads globally, default: `true` |
|
|
773
|
+
| explicitLinks | boolean | Disables all anchors being intercepted and instead requires `<A>`. Default: `false`. (To disable interception for a specific link, set `target` to any value, e.g. `<a target="_self">`.) |
|
|
774
|
+
|
|
775
|
+
### `<A>`
|
|
776
|
+
|
|
777
|
+
Like the `<a>` tag but supports automatic apply of base path + relative paths and active class styling (requires client side JavaScript).
|
|
778
|
+
|
|
779
|
+
The `<A>` tag has an `active` class if its href matches the current location, and `inactive` otherwise. **Note:** By default matching includes locations that are descendants (eg. href `/users` matches locations `/users` and `/users/123`), use the boolean `end` prop to prevent matching these. This is particularly useful for links to the root route `/` which would match everything.
|
|
780
|
+
|
|
781
|
+
| prop | type | description |
|
|
782
|
+
| ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
783
|
+
| href | string | The path of the route to navigate to. This will be resolved relative to the route that the link is in, but you can preface it with `/` to refer back to the root. |
|
|
784
|
+
| noScroll | boolean | If true, turn off the default behavior of scrolling to the top of the new page |
|
|
785
|
+
| replace | boolean | If true, don't add a new entry to the browser history. (By default, the new page will be added to the browser history, so pressing the back button will take you to the previous route.) |
|
|
786
|
+
| state | unknown | [Push this value](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) to the history stack when navigating |
|
|
787
|
+
| inactiveClass | string | The class to show when the link is inactive (when the current location doesn't match the link) |
|
|
788
|
+
| activeClass | string | The class to show when the link is active |
|
|
789
|
+
| end | boolean | If `true`, only considers the link to be active when the current location matches the `href` exactly; if `false`, check if the current location _starts with_ `href` |
|
|
790
|
+
|
|
791
|
+
### `<Navigate />`
|
|
563
792
|
|
|
564
|
-
|
|
793
|
+
Solid Router provides a `Navigate` component that works similarly to `A`, but it will _immediately_ navigate to the provided path as soon as the component is rendered. It also uses the `href` prop, but you have the additional option of passing a function to `href` that returns a path to navigate to:
|
|
565
794
|
|
|
566
|
-
```
|
|
567
|
-
|
|
795
|
+
```jsx
|
|
796
|
+
function getPath({ navigate, location }) {
|
|
797
|
+
// navigate is the result of calling useNavigate(); location is the result of calling useLocation().
|
|
798
|
+
// You can use those to dynamically determine a path to navigate to
|
|
799
|
+
return "/some-path";
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Navigating to /redirect will redirect you to the result of getPath
|
|
803
|
+
<Route path="/redirect" component={() => <Navigate href={getPath} />} />;
|
|
568
804
|
```
|
|
569
805
|
|
|
570
|
-
|
|
571
|
-
| --------------- | -------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
572
|
-
| `routes` | `RouteDefinition[]` | The route tree — inline arrays infer literally; wrap extracted trees in `defineRoutes` |
|
|
573
|
-
| `base` | `string` | Base url to use for matching routes |
|
|
574
|
-
| `preload` | `RoutePreloadFunc` | App-wide preload: once per mount/request, result reaches the root render-prop as `props.data` |
|
|
575
|
-
| `history` | `RouterHistory` | History adapter; defaults to browser history on the client and the request URL on the server |
|
|
576
|
-
| `singleFlight` | `boolean` | Single-flight mutations, default `true` |
|
|
577
|
-
| `actionBase` | `string` | Root url for server actions, default `/_server` |
|
|
578
|
-
| `preloadLinks` | `boolean` | Preload route code/data on link hover and focus, default `true` |
|
|
579
|
-
| `explicitLinks` | `boolean` | Require the `link` attribute for router handling instead of intercepting all anchors, default `false` |
|
|
580
|
-
| `transformUrl` | `(url: string) => string` | Rewrite URLs before matching |
|
|
806
|
+
### `<Route>`
|
|
581
807
|
|
|
582
|
-
The
|
|
808
|
+
The Component for defining Routes:
|
|
583
809
|
|
|
584
|
-
|
|
|
585
|
-
|
|
|
586
|
-
|
|
|
587
|
-
| `
|
|
588
|
-
| `
|
|
589
|
-
| `
|
|
810
|
+
| prop | type | description |
|
|
811
|
+
| ------------ | ------------------ | ----------------------------------------------------------------- |
|
|
812
|
+
| path | string | Path partial for defining the route segment |
|
|
813
|
+
| component | `Component` | Component that will be rendered for the matched segment |
|
|
814
|
+
| matchFilters | `MatchFilters` | Additional constraints for matching against the route |
|
|
815
|
+
| children | `JSX.Element` | Nested `<Route>` definitions |
|
|
816
|
+
| preload | `RoutePreloadFunc` | Function called during preload or when the route is navigated to. |
|
|
590
817
|
|
|
591
818
|
## Router Primitives
|
|
592
819
|
|
|
593
|
-
|
|
820
|
+
Solid Router provides a number of primitives that read off the Router and Route context.
|
|
594
821
|
|
|
595
822
|
### useParams
|
|
596
823
|
|
|
597
|
-
Retrieves a reactive, store-like object
|
|
824
|
+
Retrieves a reactive, store-like object containing the current route path parameters as defined in the Route.
|
|
598
825
|
|
|
599
|
-
```
|
|
600
|
-
const params = useParams();
|
|
601
|
-
|
|
826
|
+
```js
|
|
827
|
+
const params = useParams();
|
|
828
|
+
|
|
829
|
+
// fetch user based on the id path parameter
|
|
830
|
+
const [user] = createResource(() => params.id, fetchUser);
|
|
602
831
|
```
|
|
603
832
|
|
|
604
833
|
### useNavigate
|
|
605
834
|
|
|
606
|
-
Retrieves
|
|
835
|
+
Retrieves method to do navigation. The method accepts a path to navigate to and an optional object with the following options:
|
|
836
|
+
|
|
837
|
+
- resolve (_boolean_, default `true`): resolve the path against the current route
|
|
838
|
+
- replace (_boolean_, default `false`): replace the history entry
|
|
839
|
+
- scroll (_boolean_, default `true`): scroll to top after navigation
|
|
840
|
+
- state (_any_, default `undefined`): pass custom state to `location.state`
|
|
607
841
|
|
|
608
|
-
|
|
609
|
-
- `replace` (_boolean_, default `false`): replace the history entry
|
|
610
|
-
- `scroll` (_boolean_, default `true`): scroll to top after navigation
|
|
611
|
-
- `state` (_any_): pass custom state to `location.state` (serialized with [structured clone](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm))
|
|
842
|
+
**Note:** The state is serialized using the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) which does not support all object types.
|
|
612
843
|
|
|
613
|
-
```
|
|
844
|
+
```js
|
|
614
845
|
const navigate = useNavigate();
|
|
615
|
-
navigate(paths.login, { replace: true });
|
|
616
|
-
```
|
|
617
846
|
|
|
618
|
-
|
|
847
|
+
if (unauthorized) {
|
|
848
|
+
navigate("/login", { replace: true });
|
|
849
|
+
}
|
|
850
|
+
```
|
|
619
851
|
|
|
620
852
|
### useLocation
|
|
621
853
|
|
|
622
|
-
Retrieves
|
|
854
|
+
Retrieves reactive `location` object useful for getting things like `pathname`.
|
|
623
855
|
|
|
624
|
-
```
|
|
856
|
+
```js
|
|
625
857
|
const location = useLocation();
|
|
858
|
+
|
|
626
859
|
const pathname = createMemo(() => parsePath(location.pathname));
|
|
627
860
|
```
|
|
628
861
|
|
|
629
862
|
### useSearchParams
|
|
630
863
|
|
|
631
|
-
|
|
864
|
+
Retrieves a tuple containing a reactive object to read the current location's query parameters and a method to update them. The object is a proxy so you must access properties to subscribe to reactive updates. Note values will be strings and property names will retain their casing.
|
|
865
|
+
|
|
866
|
+
The setter method accepts an object whose entries will be merged into the current query string. Values `''`, `undefined` and `null` will remove the key from the resulting query string. Updates will behave just like a navigation and the setter accepts the same optional second parameter as `navigate` and auto-scrolling is disabled by default.
|
|
867
|
+
|
|
868
|
+
```js
|
|
869
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
870
|
+
|
|
871
|
+
return (
|
|
872
|
+
<div>
|
|
873
|
+
<span>Page: {searchParams.page}</span>
|
|
874
|
+
<button
|
|
875
|
+
onClick={() =>
|
|
876
|
+
setSearchParams({ page: (parseInt(searchParams.page) || 0) + 1 })
|
|
877
|
+
}
|
|
878
|
+
>
|
|
879
|
+
Next Page
|
|
880
|
+
</button>
|
|
881
|
+
</div>
|
|
882
|
+
);
|
|
883
|
+
```
|
|
632
884
|
|
|
633
885
|
### useIsRouting
|
|
634
886
|
|
|
635
|
-
|
|
887
|
+
Retrieves signal that indicates whether the route is currently in a Transition. Useful for showing stale/pending state when the route resolution is Suspended during concurrent rendering.
|
|
636
888
|
|
|
637
|
-
```
|
|
889
|
+
```js
|
|
638
890
|
const isRouting = useIsRouting();
|
|
639
|
-
|
|
891
|
+
|
|
892
|
+
return (
|
|
893
|
+
<div classList={{ "grey-out": isRouting() }}>
|
|
894
|
+
<MyAwesomeContent />
|
|
895
|
+
</div>
|
|
896
|
+
);
|
|
640
897
|
```
|
|
641
898
|
|
|
642
899
|
### useMatch
|
|
643
900
|
|
|
644
|
-
|
|
901
|
+
`useMatch` takes an accessor that returns the path and creates a Memo that returns match information if the current path matches the provided path. Useful for determining if a given path matches the current route.
|
|
902
|
+
|
|
903
|
+
```js
|
|
904
|
+
const match = useMatch(() => props.href);
|
|
645
905
|
|
|
646
|
-
|
|
647
|
-
const match = useMatch(() => "/admin/*rest");
|
|
648
|
-
return <Show when={match()}>...</Show>;
|
|
906
|
+
return <div classList={{ active: Boolean(match()) }} />;
|
|
649
907
|
```
|
|
650
908
|
|
|
651
|
-
###
|
|
909
|
+
### useCurrentMatches
|
|
910
|
+
|
|
911
|
+
`useCurrentMatches` returns all the matches for the current matched route. Useful for getting all the route information.
|
|
912
|
+
|
|
913
|
+
For example if you stored breadcrumbs on your route definition you could retrieve them like so:
|
|
652
914
|
|
|
653
|
-
|
|
915
|
+
```js
|
|
916
|
+
const matches = useCurrentMatches();
|
|
654
917
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
918
|
+
const breadcrumbs = createMemo(() =>
|
|
919
|
+
matches().map((m) => m.route.info.breadcrumb)
|
|
920
|
+
);
|
|
658
921
|
```
|
|
659
922
|
|
|
660
923
|
### usePreloadRoute
|
|
661
924
|
|
|
662
|
-
|
|
925
|
+
`usePreloadRoute` returns a function that can be used to preload a route manual. This is what happens automatically with link hovering and similar focus based behavior, but it is available here as an API.
|
|
663
926
|
|
|
664
|
-
```
|
|
927
|
+
```js
|
|
665
928
|
const preload = usePreloadRoute();
|
|
666
|
-
preload(paths.users(2).settings, { preloadData: true });
|
|
667
|
-
```
|
|
668
929
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
Reactive `active`/`current`/`pending` state for [custom link components](#links).
|
|
930
|
+
preload(`/users/settings`, { preloadData: true });
|
|
931
|
+
```
|
|
672
932
|
|
|
673
933
|
### useBeforeLeave
|
|
674
934
|
|
|
675
|
-
|
|
935
|
+
`useBeforeLeave` takes a function that will be called prior to leaving a route. The function will be called with:
|
|
676
936
|
|
|
677
|
-
-
|
|
678
|
-
-
|
|
679
|
-
-
|
|
680
|
-
-
|
|
681
|
-
-
|
|
682
|
-
-
|
|
937
|
+
- from (_Location_): current location (before change).
|
|
938
|
+
- to (_string | number_): path passed to `navigate`.
|
|
939
|
+
- options (_NavigateOptions_): options passed to `navigate`.
|
|
940
|
+
- preventDefault (_function_): call to block the route change.
|
|
941
|
+
- defaultPrevented (_readonly boolean_): `true` if any previously called leave handlers called `preventDefault`.
|
|
942
|
+
- retry (_function_, _force?: boolean_ ): call to retry the same navigation, perhaps after confirming with the user. Pass `true` to skip running the leave handlers again (i.e. force navigate without confirming).
|
|
683
943
|
|
|
684
|
-
|
|
944
|
+
Example usage:
|
|
945
|
+
|
|
946
|
+
```js
|
|
685
947
|
useBeforeLeave((e: BeforeLeaveEventArgs) => {
|
|
686
948
|
if (form.isDirty && !e.defaultPrevented) {
|
|
949
|
+
// preventDefault to block immediately and prompt user async
|
|
687
950
|
e.preventDefault();
|
|
688
951
|
setTimeout(() => {
|
|
689
952
|
if (window.confirm("Discard unsaved changes - are you sure?")) {
|
|
953
|
+
// user wants to proceed anyway so retry with force=true
|
|
690
954
|
e.retry(true);
|
|
691
955
|
}
|
|
692
956
|
}, 100);
|
|
@@ -694,153 +958,73 @@ useBeforeLeave((e: BeforeLeaveEventArgs) => {
|
|
|
694
958
|
});
|
|
695
959
|
```
|
|
696
960
|
|
|
697
|
-
##
|
|
961
|
+
## Migrations from 0.9.x
|
|
698
962
|
|
|
699
|
-
|
|
963
|
+
v0.10.0 brings some big changes to support the future of routing including Islands/Partial Hydration hybrid solutions. Most notably there is no Context API available in non-hydrating parts of the application.
|
|
700
964
|
|
|
701
|
-
|
|
702
|
-
import { createRouter, hashHistory, memoryHistory } from "@solidjs/router";
|
|
965
|
+
The biggest changes are around removed APIs that need to be replaced.
|
|
703
966
|
|
|
704
|
-
|
|
705
|
-
const Router = createRouter({ routes, history: hashHistory() });
|
|
967
|
+
### `<Outlet>`, `<Routes>`, `useRoutes`
|
|
706
968
|
|
|
707
|
-
|
|
708
|
-
const Router = createRouter({ routes, history: memoryHistory("/users/1") });
|
|
709
|
-
```
|
|
969
|
+
This is no longer used and instead will use `props.children` passed from into the page components for outlets. This keeps the outlet directly passed from its page and avoids oddness of trying to use context across Islands boundaries. Nested `<Routes>` components inherently cause waterfalls and are `<Outlets>` themselves so they have the same concerns.
|
|
710
970
|
|
|
711
|
-
|
|
971
|
+
Keep in mind no `<Routes>` means the `<Router>` API is different. The `<Router>` acts as the `<Routes>` component and its children can only be `<Route>` components. Your top-level layout should go in the root prop of the router [as shown above](#configure-your-routes)
|
|
712
972
|
|
|
713
|
-
|
|
973
|
+
## `element` prop removed from `Route`
|
|
714
974
|
|
|
715
|
-
|
|
716
|
-
const base = browserHistory();
|
|
717
|
-
const history = {
|
|
718
|
-
...base,
|
|
719
|
-
// wrappers build objects with defined getters instead of a Proxy
|
|
720
|
-
utils: { ...base.utils, paramsWrapper, queryWrapper }
|
|
721
|
-
};
|
|
722
|
-
const Router = createRouter({ routes, history });
|
|
723
|
-
```
|
|
975
|
+
Related without Outlet component it has to be passed in manually. At which point the `element` prop has less value. Removing the second way to define route components to reduce confusion and edge cases.
|
|
724
976
|
|
|
725
|
-
|
|
977
|
+
### `data` functions & `useRouteData`
|
|
726
978
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
```tsx
|
|
730
|
-
import { Router } from "./router";
|
|
731
|
-
|
|
732
|
-
Router.match("/users/2/settings?tab=x");
|
|
733
|
-
// [
|
|
734
|
-
// { path: "/users/:id", match: "/users/2", params: { id: "2" } },
|
|
735
|
-
// { path: "/settings", match: "/users/2/settings", params: {} }
|
|
736
|
-
// ]
|
|
737
|
-
```
|
|
979
|
+
These have been replaced by a preload mechanism. This allows link hover preloads (as the preload function can be run as much as wanted without worry about reactivity). It support deduping/query APIs which give more control over how things are cached. It also addresses TS issues with getting the right types in the Component without `typeof` checks.
|
|
738
980
|
|
|
739
|
-
|
|
981
|
+
That being said you can reproduce the old pattern largely by turning off preloads at the router level and then injecting your own Context:
|
|
740
982
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
import { createFlightDataCollector, createNoJSHandler } from "@solidjs/router/server";
|
|
745
|
-
import { Router } from "./app/router";
|
|
746
|
-
|
|
747
|
-
const collectFlightData = createFlightDataCollector(Router);
|
|
748
|
-
const handleNoJS = createNoJSHandler();
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
`createFlightDataCollector` produces the single-flight hook: after a mutation it reruns the route data the mutation invalidated for the page the client is on (or is redirected to), folding fresh data into the same response. `createNoJSHandler` implements the no-JS form convention: form posts without the client runtime redirect back with the outcome in a one-shot flash cookie that SSR reads into submission state. Both policies previously lived inside SolidStart; the router now owns them, so custom server setups get single-flight mutations and progressive enhancement without a framework.
|
|
752
|
-
|
|
753
|
-
## Migration from 0.x
|
|
754
|
-
|
|
755
|
-
This guide maps from the stable 0.x releases (Solid 1). 1.0 removes the component-based API — the `createRouter` factory is the only way to set up the router, and plain `<a>` elements are the only link primitive. 1.0 also targets Solid 2, so the async data patterns change alongside the router API.
|
|
756
|
-
|
|
757
|
-
### Router components → `createRouter`
|
|
758
|
-
|
|
759
|
-
```tsx
|
|
760
|
-
// 0.x
|
|
761
|
-
<Router root={App}>
|
|
762
|
-
<Route path="/users" component={Users} />
|
|
763
|
-
<Route path="/users/:id" component={User} />
|
|
764
|
-
</Router>
|
|
765
|
-
|
|
766
|
-
// 1.0
|
|
767
|
-
const Router = createRouter({
|
|
768
|
-
routes: [
|
|
769
|
-
{ path: "/users", component: Users },
|
|
770
|
-
{ path: "/users/:id", component: User }
|
|
771
|
-
]
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
<Router>{props => <App {...props} />}</Router>
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
- `<HashRouter>` → `createRouter({ routes, history: hashHistory() })`
|
|
778
|
-
- `<MemoryRouter>` / `createMemoryHistory` → `createRouter({ routes, history: memoryHistory("/initial") })`
|
|
779
|
-
- `<StaticRouter url>` / `<Router url>` for SSR → automatic from the request URL; without a request event, pass `memoryHistory(url)`
|
|
780
|
-
- `root` prop → the render-prop child; `rootPreload` → the factory's `preload` option
|
|
781
|
-
|
|
782
|
-
### JSX `<Route>` → config objects
|
|
783
|
-
|
|
784
|
-
Route props map 1:1 onto definition keys (`path`, `component`, `preload`, `matchFilters`, `info`); nesting becomes `children` arrays. Wrap extracted route trees in `defineRoutes` to get typed `paths`. File-based routing generates config.
|
|
785
|
-
|
|
786
|
-
### `<A>` → plain `<a>`
|
|
787
|
-
|
|
788
|
-
- `<A href replace noScroll state>` → `<a href replace noscroll state>` (attributes, all lowercase)
|
|
789
|
-
- `activeClass` / `inactiveClass` → CSS attribute selectors on `[data-active]` / `[aria-current="page"]`
|
|
790
|
-
- `end` → style exact matches with `[aria-current="page"]` instead of `[data-active]`; the root path already only matches exactly
|
|
791
|
-
- Route-relative hrefs → typed `paths`; `useResolvedPath` / `useHref` remain for manual resolution
|
|
792
|
-
- Custom link components → `useLinkState`
|
|
793
|
-
|
|
794
|
-
### Removed and renamed
|
|
795
|
-
|
|
796
|
-
- `<Navigate>` → call `useNavigate()` during component setup, or redirect from a preload
|
|
797
|
-
- `useCurrentMatches` → `useRouteMatches` (same behavior)
|
|
798
|
-
- `redirect` / `reload` → import from `@solidjs/web`; they're protocol-level and work without the router
|
|
799
|
-
- `json(data, init)` → `respond(data, init)` from `@solidjs/web`
|
|
800
|
-
- `cache` (deprecated alias) → `query`
|
|
801
|
-
|
|
802
|
-
### Data APIs (Solid 2)
|
|
983
|
+
```js
|
|
984
|
+
import { lazy } from "solid-js";
|
|
985
|
+
import { Route } from "@solidjs/router";
|
|
803
986
|
|
|
804
|
-
|
|
987
|
+
const User = lazy(() => import("./pages/users/[id].js"));
|
|
805
988
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
const user =
|
|
989
|
+
// preload function
|
|
990
|
+
function preloadUser({ params, location }) {
|
|
991
|
+
const [user] = createResource(() => params.id, fetchUser);
|
|
992
|
+
return user;
|
|
993
|
+
}
|
|
809
994
|
|
|
810
|
-
//
|
|
811
|
-
|
|
995
|
+
// Pass it in the route definition
|
|
996
|
+
<Router preload={false}>
|
|
997
|
+
<Route path="/users/:id" component={User} preload={preloadUser} />
|
|
998
|
+
</Router>;
|
|
812
999
|
```
|
|
813
1000
|
|
|
814
|
-
|
|
815
|
-
- `useSubmission` (singular) is gone, and submissions are now settled history rather than in-flight state. Pending/optimistic UI moves to Solid's optimistic primitives fed by the action's `.onSubmit(...)` hook; read settled results with `useSubmissions()` and select the latest with `.at(-1)`.
|
|
1001
|
+
And then in your component taking the page props and putting them in a Context.
|
|
816
1002
|
|
|
817
|
-
```
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1003
|
+
```js
|
|
1004
|
+
function User(props) {
|
|
1005
|
+
<UserContext.Provider value={props.data}>
|
|
1006
|
+
{/* my component content */}
|
|
1007
|
+
</UserContext.Provider>;
|
|
1008
|
+
}
|
|
821
1009
|
|
|
822
|
-
//
|
|
823
|
-
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
})
|
|
828
|
-
);
|
|
1010
|
+
// Somewhere else
|
|
1011
|
+
function UserDetails() {
|
|
1012
|
+
const user = useContext(UserContext);
|
|
1013
|
+
// render stuff
|
|
1014
|
+
}
|
|
829
1015
|
```
|
|
830
1016
|
|
|
831
|
-
- Action lifecycle centers on instance methods: `.onSubmit(...)` for owner-scoped optimistic work, `.onSettled(...)` for observing completions. Returned values are the expected result channel; thrown errors land on `Submission.error`.
|
|
832
|
-
|
|
833
1017
|
## SPAs in Deployed Environments
|
|
834
1018
|
|
|
835
|
-
When deploying a client
|
|
1019
|
+
When deploying applications that use a client side router that does not rely on Server Side Rendering you need to handle redirects to your index page so that loading from other URLs does not cause your CDN or Hosting to return not found for pages that aren't actually there.
|
|
836
1020
|
|
|
837
|
-
|
|
1021
|
+
Each provider has a different way of doing this. For example on Netlify you create a `_redirects` file that contains:
|
|
838
1022
|
|
|
839
1023
|
```sh
|
|
840
1024
|
/* /index.html 200
|
|
841
1025
|
```
|
|
842
1026
|
|
|
843
|
-
On Vercel
|
|
1027
|
+
On Vercel you add a rewrites section to your `vercel.json`:
|
|
844
1028
|
|
|
845
1029
|
```json
|
|
846
1030
|
{
|