@tanstack/solid-router 2.0.0-alpha.4 → 2.0.0-alpha.5
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/bin/intent.js +25 -0
- package/package.json +11 -4
- package/skills/solid-router/SKILL.md +497 -0
package/bin/intent.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Auto-generated by @tanstack/intent setup
|
|
3
|
+
// Exposes the intent end-user CLI for consumers of this library.
|
|
4
|
+
// Commit this file, then add to your package.json:
|
|
5
|
+
// "bin": { "intent": "./bin/intent.js" }
|
|
6
|
+
try {
|
|
7
|
+
await import('@tanstack/intent/intent-library')
|
|
8
|
+
} catch (e) {
|
|
9
|
+
const isModuleNotFound =
|
|
10
|
+
e?.code === 'ERR_MODULE_NOT_FOUND' || e?.code === 'MODULE_NOT_FOUND'
|
|
11
|
+
const missingIntentLibrary =
|
|
12
|
+
typeof e?.message === 'string' && e.message.includes('@tanstack/intent')
|
|
13
|
+
|
|
14
|
+
if (isModuleNotFound && missingIntentLibrary) {
|
|
15
|
+
console.error('@tanstack/intent is not installed.')
|
|
16
|
+
console.error('')
|
|
17
|
+
console.error('Install it as a dev dependency:')
|
|
18
|
+
console.error(' npm add -D @tanstack/intent')
|
|
19
|
+
console.error('')
|
|
20
|
+
console.error('Or run directly:')
|
|
21
|
+
console.error(' npx @tanstack/intent@latest list')
|
|
22
|
+
process.exit(1)
|
|
23
|
+
}
|
|
24
|
+
throw e
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/solid-router",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.5",
|
|
4
4
|
"description": "Modern and scalable routing for Solid applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -78,7 +78,10 @@
|
|
|
78
78
|
"sideEffects": false,
|
|
79
79
|
"files": [
|
|
80
80
|
"dist",
|
|
81
|
-
"src"
|
|
81
|
+
"src",
|
|
82
|
+
"skills",
|
|
83
|
+
"bin",
|
|
84
|
+
"!skills/_artifacts"
|
|
82
85
|
],
|
|
83
86
|
"engines": {
|
|
84
87
|
"node": ">=20.19"
|
|
@@ -91,23 +94,27 @@
|
|
|
91
94
|
"tiny-invariant": "^1.3.3",
|
|
92
95
|
"tiny-warning": "^1.0.3",
|
|
93
96
|
"@tanstack/history": "1.161.6",
|
|
94
|
-
"@tanstack/router-core": "1.167.
|
|
97
|
+
"@tanstack/router-core": "1.167.5"
|
|
95
98
|
},
|
|
96
99
|
"devDependencies": {
|
|
97
100
|
"@solidjs/testing-library": "^0.8.10",
|
|
101
|
+
"@tanstack/intent": "^0.0.14",
|
|
98
102
|
"@testing-library/jest-dom": "^6.6.3",
|
|
99
103
|
"combinate": "^1.1.11",
|
|
100
104
|
"eslint-plugin-solid": "^0.14.5",
|
|
101
105
|
"solid-js": "2.0.0-beta.3",
|
|
102
106
|
"@solidjs/web": "2.0.0-beta.3",
|
|
103
107
|
"vite": "*",
|
|
104
|
-
"vite-plugin-solid": "3.0.0-next.
|
|
108
|
+
"vite-plugin-solid": "3.0.0-next.3",
|
|
105
109
|
"zod": "^3.23.8"
|
|
106
110
|
},
|
|
107
111
|
"peerDependencies": {
|
|
108
112
|
"solid-js": "2.0.0-beta.3",
|
|
109
113
|
"@solidjs/web": "2.0.0-beta.3"
|
|
110
114
|
},
|
|
115
|
+
"bin": {
|
|
116
|
+
"intent": "./bin/intent.js"
|
|
117
|
+
},
|
|
111
118
|
"scripts": {
|
|
112
119
|
"clean": "rimraf ./dist && rimraf ./coverage",
|
|
113
120
|
"test:eslint": "eslint",
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: solid-router
|
|
3
|
+
description: >-
|
|
4
|
+
Solid bindings for TanStack Router: RouterProvider, useRouter,
|
|
5
|
+
useRouterState, useMatch, useMatches, useLocation, useSearch,
|
|
6
|
+
useParams, useNavigate, useLoaderData, useLoaderDeps,
|
|
7
|
+
useRouteContext, useBlocker, useCanGoBack, Link, Navigate,
|
|
8
|
+
Outlet, CatchBoundary, ErrorComponent. Solid-specific patterns
|
|
9
|
+
with Accessor<T> returns, createSignal/createMemo/createEffect,
|
|
10
|
+
Show/Switch/Match/Dynamic, and @solidjs/meta for head management.
|
|
11
|
+
type: framework
|
|
12
|
+
library: tanstack-router
|
|
13
|
+
library_version: '1.166.2'
|
|
14
|
+
framework: solid
|
|
15
|
+
requires:
|
|
16
|
+
- router-core
|
|
17
|
+
sources:
|
|
18
|
+
- TanStack/router:packages/solid-router/src
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Solid Router (`@tanstack/solid-router`)
|
|
22
|
+
|
|
23
|
+
This skill builds on router-core. Read [router-core](../../../router-core/skills/router-core/SKILL.md) first for foundational concepts.
|
|
24
|
+
|
|
25
|
+
This skill covers the Solid-specific bindings, components, hooks, and setup for TanStack Router.
|
|
26
|
+
|
|
27
|
+
> **CRITICAL**: TanStack Router types are FULLY INFERRED. Never cast, never annotate inferred values.
|
|
28
|
+
> **CRITICAL**: TanStack Router is CLIENT-FIRST. Loaders run on the client by default, not on the server.
|
|
29
|
+
> **CRITICAL**: Most hooks return `Accessor<T>` — you MUST call the accessor (`value()`) to read the reactive value. This is the #1 difference from the React version.
|
|
30
|
+
> **CRITICAL**: Do not confuse `@tanstack/solid-router` with `@solidjs/router`. They are completely different libraries with different APIs.
|
|
31
|
+
|
|
32
|
+
## Full Setup: File-Based Routing with Vite
|
|
33
|
+
|
|
34
|
+
### 1. Install Dependencies
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install @tanstack/solid-router
|
|
38
|
+
npm install -D @tanstack/router-plugin @tanstack/solid-router-devtools
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Configure Vite Plugin
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// vite.config.ts
|
|
45
|
+
import { defineConfig } from 'vite'
|
|
46
|
+
import solidPlugin from 'vite-plugin-solid'
|
|
47
|
+
import { tanstackRouter } from '@tanstack/router-plugin/vite'
|
|
48
|
+
|
|
49
|
+
export default defineConfig({
|
|
50
|
+
plugins: [
|
|
51
|
+
// MUST come before solid plugin
|
|
52
|
+
tanstackRouter({
|
|
53
|
+
target: 'solid',
|
|
54
|
+
autoCodeSplitting: true,
|
|
55
|
+
}),
|
|
56
|
+
solidPlugin(),
|
|
57
|
+
],
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 3. Create Root Route
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// src/routes/__root.tsx
|
|
65
|
+
import { createRootRoute, Link, Outlet } from '@tanstack/solid-router'
|
|
66
|
+
|
|
67
|
+
export const Route = createRootRoute({
|
|
68
|
+
component: RootLayout,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
function RootLayout() {
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
<nav>
|
|
75
|
+
<Link to="/" activeClass="font-bold">
|
|
76
|
+
Home
|
|
77
|
+
</Link>
|
|
78
|
+
<Link to="/about" activeClass="font-bold">
|
|
79
|
+
About
|
|
80
|
+
</Link>
|
|
81
|
+
</nav>
|
|
82
|
+
<hr />
|
|
83
|
+
<Outlet />
|
|
84
|
+
</>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 4. Create Route Files
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
// src/routes/index.tsx
|
|
93
|
+
import { createFileRoute } from '@tanstack/solid-router'
|
|
94
|
+
|
|
95
|
+
export const Route = createFileRoute('/')({
|
|
96
|
+
component: HomePage,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
function HomePage() {
|
|
100
|
+
return <h1>Welcome Home</h1>
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 5. Create Router Instance and Register Types
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
// src/main.tsx
|
|
108
|
+
import { render } from 'solid-js/web'
|
|
109
|
+
import { RouterProvider, createRouter } from '@tanstack/solid-router'
|
|
110
|
+
import { routeTree } from './routeTree.gen'
|
|
111
|
+
|
|
112
|
+
const router = createRouter({ routeTree })
|
|
113
|
+
|
|
114
|
+
// REQUIRED — without this, Link/useNavigate/useSearch have no type safety
|
|
115
|
+
declare module '@tanstack/solid-router' {
|
|
116
|
+
interface Register {
|
|
117
|
+
router: typeof router
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
render(
|
|
122
|
+
() => <RouterProvider router={router} />,
|
|
123
|
+
document.getElementById('root')!,
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Hooks Reference
|
|
128
|
+
|
|
129
|
+
All hooks imported from `@tanstack/solid-router`. Most return `Accessor<T>` — call the result to read the value.
|
|
130
|
+
|
|
131
|
+
### `useRouter()` — returns `TRouter` (NOT an Accessor)
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
import { useRouter } from '@tanstack/solid-router'
|
|
135
|
+
|
|
136
|
+
function InvalidateButton() {
|
|
137
|
+
const router = useRouter()
|
|
138
|
+
return <button onClick={() => router.invalidate()}>Refresh data</button>
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `useRouterState()` — returns `Accessor<T>`
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { useRouterState } from '@tanstack/solid-router'
|
|
146
|
+
|
|
147
|
+
function LoadingIndicator() {
|
|
148
|
+
const isLoading = useRouterState({ select: (s) => s.isLoading })
|
|
149
|
+
return (
|
|
150
|
+
<Show when={isLoading()}>
|
|
151
|
+
<div>Loading...</div>
|
|
152
|
+
</Show>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `useNavigate()` — returns a function (NOT an Accessor)
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { useNavigate } from '@tanstack/solid-router'
|
|
161
|
+
|
|
162
|
+
function AfterSubmit() {
|
|
163
|
+
const navigate = useNavigate()
|
|
164
|
+
|
|
165
|
+
const handleSubmit = async () => {
|
|
166
|
+
await saveData()
|
|
167
|
+
navigate({ to: '/posts/$postId', params: { postId: '123' } })
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return <button onClick={handleSubmit}>Save</button>
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### `useSearch({ from })` — returns `Accessor<T>`
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { useSearch } from '@tanstack/solid-router'
|
|
178
|
+
|
|
179
|
+
function Pagination() {
|
|
180
|
+
const search = useSearch({ from: '/products' })
|
|
181
|
+
return <span>Page {search().page}</span>
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### `useParams({ from })` — returns `Accessor<T>`
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
import { useParams } from '@tanstack/solid-router'
|
|
189
|
+
|
|
190
|
+
function PostHeader() {
|
|
191
|
+
const params = useParams({ from: '/posts/$postId' })
|
|
192
|
+
return <h2>Post {params().postId}</h2>
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### `useLoaderData({ from })` — returns `Accessor<T>`
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
import { useLoaderData } from '@tanstack/solid-router'
|
|
200
|
+
|
|
201
|
+
function PostContent() {
|
|
202
|
+
const data = useLoaderData({ from: '/posts/$postId' })
|
|
203
|
+
return <article>{data().post.content}</article>
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `useMatch({ from })` — returns `Accessor<T>`
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { useMatch } from '@tanstack/solid-router'
|
|
211
|
+
|
|
212
|
+
function PostDetails() {
|
|
213
|
+
const match = useMatch({ from: '/posts/$postId' })
|
|
214
|
+
return <div>{match().loaderData.post.title}</div>
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Other Hooks
|
|
219
|
+
|
|
220
|
+
All imported from `@tanstack/solid-router`:
|
|
221
|
+
|
|
222
|
+
- **`useMatches()`** — `Accessor<Array<Match>>`, all active route matches
|
|
223
|
+
- **`useParentMatches()`** — `Accessor<Array<Match>>`, parent route matches
|
|
224
|
+
- **`useChildMatches()`** — `Accessor<Array<Match>>`, child route matches
|
|
225
|
+
- **`useRouteContext({ from })`** — `Accessor<T>`, context from `beforeLoad`
|
|
226
|
+
- **`useLoaderDeps({ from })`** — `Accessor<T>`, loader dependency values
|
|
227
|
+
- **`useBlocker({ shouldBlockFn })`** — blocks navigation for unsaved changes
|
|
228
|
+
- **`useCanGoBack()`** — `Accessor<boolean>`
|
|
229
|
+
- **`useLocation()`** — `Accessor<ParsedLocation>`
|
|
230
|
+
- **`useLinkProps({ to, params?, search? })`** — returns `ComponentProps<'a'>` (NOT an Accessor)
|
|
231
|
+
- **`useMatchRoute()`** — returns a function; calling it returns `Accessor<false | Params>`
|
|
232
|
+
- **`useHydrated()`** — `Accessor<boolean>`
|
|
233
|
+
|
|
234
|
+
## Components Reference
|
|
235
|
+
|
|
236
|
+
### `RouterProvider`
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
<RouterProvider router={router} />
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### `Link`
|
|
243
|
+
|
|
244
|
+
Type-safe navigation link. Children can be a function for active state:
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
;<Link to="/posts/$postId" params={{ postId: '42' }}>
|
|
248
|
+
View Post
|
|
249
|
+
</Link>
|
|
250
|
+
|
|
251
|
+
{
|
|
252
|
+
/* Function children for active state */
|
|
253
|
+
}
|
|
254
|
+
;<Link to="/about">
|
|
255
|
+
{(state) => <span classList={{ active: state.isActive }}>About</span>}
|
|
256
|
+
</Link>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### `Outlet`
|
|
260
|
+
|
|
261
|
+
Renders the matched child route component:
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
function Layout() {
|
|
265
|
+
return (
|
|
266
|
+
<div>
|
|
267
|
+
<Sidebar />
|
|
268
|
+
<main>
|
|
269
|
+
<Outlet />
|
|
270
|
+
</main>
|
|
271
|
+
</div>
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `Navigate`
|
|
277
|
+
|
|
278
|
+
Declarative redirect (triggers navigation in `onMount`):
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
import { Navigate } from '@tanstack/solid-router'
|
|
282
|
+
|
|
283
|
+
function OldPage() {
|
|
284
|
+
return <Navigate to="/new-page" />
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### `Await`
|
|
289
|
+
|
|
290
|
+
Renders deferred data with Solid's `Suspense`:
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
import { Await } from '@tanstack/solid-router'
|
|
294
|
+
import { Suspense } from 'solid-js'
|
|
295
|
+
|
|
296
|
+
function PostWithComments() {
|
|
297
|
+
const data = Route.useLoaderData()
|
|
298
|
+
return (
|
|
299
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
300
|
+
<Await promise={data().deferredComments}>
|
|
301
|
+
{(comments) => <For each={comments}>{(c) => <li>{c.text}</li>}</For>}
|
|
302
|
+
</Await>
|
|
303
|
+
</Suspense>
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### `CatchBoundary`
|
|
309
|
+
|
|
310
|
+
Error boundary wrapping `Solid.ErrorBoundary`:
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
import { CatchBoundary } from '@tanstack/solid-router'
|
|
314
|
+
;<CatchBoundary
|
|
315
|
+
getResetKey={() => 'widget'}
|
|
316
|
+
errorComponent={({ error }) => <div>Error: {error.message}</div>}
|
|
317
|
+
>
|
|
318
|
+
<RiskyWidget />
|
|
319
|
+
</CatchBoundary>
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Other Components
|
|
323
|
+
|
|
324
|
+
- **`CatchNotFound`** — catches `notFound()` errors in children; `fallback` receives the error data
|
|
325
|
+
- **`Block`** — declarative navigation blocker; use `shouldBlockFn` and `withResolver` for custom UI
|
|
326
|
+
- **`ScrollRestoration`** — **deprecated**; use `createRouter`'s `scrollRestoration: true` option instead
|
|
327
|
+
- **`ClientOnly`** — renders children only after hydration; accepts `fallback` prop
|
|
328
|
+
|
|
329
|
+
### `Block`
|
|
330
|
+
|
|
331
|
+
Declarative navigation blocker component:
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
import { Block } from '@tanstack/solid-router'
|
|
335
|
+
;<Block shouldBlockFn={() => formIsDirty()} withResolver>
|
|
336
|
+
{({ status, proceed, reset }) => (
|
|
337
|
+
<Show when={status === 'blocked'}>
|
|
338
|
+
<div>
|
|
339
|
+
<p>Are you sure?</p>
|
|
340
|
+
<button onClick={proceed}>Yes</button>
|
|
341
|
+
<button onClick={reset}>No</button>
|
|
342
|
+
</div>
|
|
343
|
+
</Show>
|
|
344
|
+
)}
|
|
345
|
+
</Block>
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### `ScrollRestoration`
|
|
349
|
+
|
|
350
|
+
Restores scroll position on navigation:
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
import { ScrollRestoration } from '@tanstack/solid-router'
|
|
354
|
+
// In root route component
|
|
355
|
+
;<ScrollRestoration />
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### `ClientOnly`
|
|
359
|
+
|
|
360
|
+
Renders children only after hydration:
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
import { ClientOnly } from '@tanstack/solid-router'
|
|
364
|
+
;<ClientOnly fallback={<div>Loading...</div>}>
|
|
365
|
+
<BrowserOnlyWidget />
|
|
366
|
+
</ClientOnly>
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Head Management
|
|
370
|
+
|
|
371
|
+
Uses `@solidjs/meta` under the hood:
|
|
372
|
+
|
|
373
|
+
```tsx
|
|
374
|
+
import { HeadContent, Scripts } from '@tanstack/solid-router'
|
|
375
|
+
|
|
376
|
+
function RootDocument(props) {
|
|
377
|
+
return (
|
|
378
|
+
<html>
|
|
379
|
+
<head>
|
|
380
|
+
<HeadContent />
|
|
381
|
+
</head>
|
|
382
|
+
<body>
|
|
383
|
+
{props.children}
|
|
384
|
+
<Scripts />
|
|
385
|
+
</body>
|
|
386
|
+
</html>
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Solid-Specific Patterns
|
|
392
|
+
|
|
393
|
+
### Custom Link Component with `createLink`
|
|
394
|
+
|
|
395
|
+
```tsx
|
|
396
|
+
import { createLink } from '@tanstack/solid-router'
|
|
397
|
+
|
|
398
|
+
const StyledLinkComponent = (props) => (
|
|
399
|
+
<a {...props} class={`styled-link ${props.class ?? ''}`} />
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
const StyledLink = createLink(StyledLinkComponent)
|
|
403
|
+
|
|
404
|
+
function Nav() {
|
|
405
|
+
return (
|
|
406
|
+
<StyledLink to="/posts/$postId" params={{ postId: '42' }}>
|
|
407
|
+
Post
|
|
408
|
+
</StyledLink>
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Using Solid Primitives with Router State
|
|
414
|
+
|
|
415
|
+
```tsx
|
|
416
|
+
import { createMemo, Show, For } from 'solid-js'
|
|
417
|
+
import { useRouterState } from '@tanstack/solid-router'
|
|
418
|
+
|
|
419
|
+
function Breadcrumbs() {
|
|
420
|
+
const matches = useRouterState({ select: (s) => s.matches })
|
|
421
|
+
const crumbs = createMemo(() =>
|
|
422
|
+
matches().filter((m) => m.context?.breadcrumb),
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
return (
|
|
426
|
+
<nav>
|
|
427
|
+
<For each={crumbs()}>
|
|
428
|
+
{(match) => <span>{match.context.breadcrumb}</span>}
|
|
429
|
+
</For>
|
|
430
|
+
</nav>
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Auth with Router Context
|
|
436
|
+
|
|
437
|
+
```tsx
|
|
438
|
+
import { createRootRouteWithContext } from '@tanstack/solid-router'
|
|
439
|
+
|
|
440
|
+
const rootRoute = createRootRouteWithContext<{ auth: AuthState }>()({
|
|
441
|
+
component: RootComponent,
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
// In main.tsx — provide context at router creation
|
|
445
|
+
const router = createRouter({
|
|
446
|
+
routeTree,
|
|
447
|
+
context: { auth: authState },
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
// In a route — access via beforeLoad (NOT hooks)
|
|
451
|
+
beforeLoad: ({ context }) => {
|
|
452
|
+
if (!context.auth.isAuthenticated) {
|
|
453
|
+
throw redirect({ to: '/login' })
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## Common Mistakes
|
|
459
|
+
|
|
460
|
+
### 1. CRITICAL: Forgetting to call Accessor
|
|
461
|
+
|
|
462
|
+
Hooks return `Accessor<T>` — you must call them to read the value. This is the #1 migration issue from React.
|
|
463
|
+
|
|
464
|
+
```tsx
|
|
465
|
+
// WRONG — comparing the accessor function, not its value
|
|
466
|
+
const params = useParams({ from: '/posts/$postId' })
|
|
467
|
+
if (params.postId === '42') { ... } // params is a function!
|
|
468
|
+
|
|
469
|
+
// CORRECT — call the accessor
|
|
470
|
+
const params = useParams({ from: '/posts/$postId' })
|
|
471
|
+
if (params().postId === '42') { ... }
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### 2. HIGH: Destructuring reactive values
|
|
475
|
+
|
|
476
|
+
Destructuring breaks Solid's reactivity tracking.
|
|
477
|
+
|
|
478
|
+
```tsx
|
|
479
|
+
// WRONG — loses reactivity
|
|
480
|
+
const { page } = useSearch({ from: '/products' })()
|
|
481
|
+
|
|
482
|
+
// CORRECT — access through accessor
|
|
483
|
+
const search = useSearch({ from: '/products' })
|
|
484
|
+
<span>Page {search().page}</span>
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### 3. HIGH: Using React hooks in beforeLoad or loader
|
|
488
|
+
|
|
489
|
+
`beforeLoad` and `loader` are NOT components — they are plain async functions. No hooks (React or Solid) can be used in them. Pass state via router context instead.
|
|
490
|
+
|
|
491
|
+
### 4. MEDIUM: Wrong plugin target
|
|
492
|
+
|
|
493
|
+
Must set `target: 'solid'` in the router plugin config. Default is `'react'`.
|
|
494
|
+
|
|
495
|
+
## Cross-References
|
|
496
|
+
|
|
497
|
+
- [router-core/SKILL.md](../../../router-core/skills/router-core/SKILL.md) — all sub-skills for domain-specific patterns (search params, data loading, navigation, auth, SSR, etc.)
|