@tanstack/router-core 1.167.2 → 1.167.4
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/dist/cjs/load-matches.cjs +4 -1
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/router.cjs +2 -1
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/esm/load-matches.js +4 -1
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/router.js +2 -1
- package/dist/esm/router.js.map +1 -1
- package/package.json +9 -2
- package/skills/router-core/SKILL.md +139 -0
- package/skills/router-core/auth-and-guards/SKILL.md +458 -0
- package/skills/router-core/code-splitting/SKILL.md +322 -0
- package/skills/router-core/data-loading/SKILL.md +485 -0
- package/skills/router-core/navigation/SKILL.md +448 -0
- package/skills/router-core/not-found-and-errors/SKILL.md +435 -0
- package/skills/router-core/path-params/SKILL.md +382 -0
- package/skills/router-core/search-params/SKILL.md +355 -0
- package/skills/router-core/search-params/references/validation-patterns.md +379 -0
- package/skills/router-core/ssr/SKILL.md +437 -0
- package/skills/router-core/type-safety/SKILL.md +497 -0
- package/src/load-matches.ts +4 -1
- package/src/router.ts +2 -1
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router-core/search-params
|
|
3
|
+
description: >-
|
|
4
|
+
validateSearch, search param validation with Zod/Valibot/ArkType adapters,
|
|
5
|
+
fallback(), search middlewares (retainSearchParams, stripSearchParams),
|
|
6
|
+
custom serialization (parseSearch, stringifySearch), search param
|
|
7
|
+
inheritance, loaderDeps for cache keys, reading and writing search params.
|
|
8
|
+
type: sub-skill
|
|
9
|
+
library: tanstack-router
|
|
10
|
+
library_version: '1.166.2'
|
|
11
|
+
requires:
|
|
12
|
+
- router-core
|
|
13
|
+
sources:
|
|
14
|
+
- TanStack/router:docs/router/guide/search-params.md
|
|
15
|
+
- TanStack/router:docs/router/how-to/setup-basic-search-params.md
|
|
16
|
+
- TanStack/router:docs/router/how-to/validate-search-params.md
|
|
17
|
+
- TanStack/router:docs/router/how-to/navigate-with-search-params.md
|
|
18
|
+
- TanStack/router:docs/router/how-to/share-search-params-across-routes.md
|
|
19
|
+
- TanStack/router:docs/router/guide/custom-search-param-serialization.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Search Params
|
|
23
|
+
|
|
24
|
+
TanStack Router treats search params as JSON-first application state. They are automatically parsed from the URL into structured objects (numbers, booleans, arrays, nested objects) and validated via `validateSearch` on each route.
|
|
25
|
+
|
|
26
|
+
> **CRITICAL**: When using `zodValidator()`, use `fallback()` from `@tanstack/zod-adapter`, NOT zod's `.catch()`. Using `.catch()` with the zod adapter makes the output type `unknown`, destroying type safety. This does not apply to Valibot or ArkType (which use their own fallback mechanisms).
|
|
27
|
+
> **CRITICAL**: Types are fully inferred. Never annotate the return of `useSearch()`.
|
|
28
|
+
|
|
29
|
+
## Setup: Zod Adapter (Recommended)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install zod @tanstack/zod-adapter
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// src/routes/products.tsx
|
|
37
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
38
|
+
import { zodValidator, fallback } from '@tanstack/zod-adapter'
|
|
39
|
+
import { z } from 'zod'
|
|
40
|
+
|
|
41
|
+
const productSearchSchema = z.object({
|
|
42
|
+
page: fallback(z.number(), 1).default(1),
|
|
43
|
+
filter: fallback(z.string(), '').default(''),
|
|
44
|
+
sort: fallback(z.enum(['newest', 'oldest', 'price']), 'newest').default(
|
|
45
|
+
'newest',
|
|
46
|
+
),
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export const Route = createFileRoute('/products')({
|
|
50
|
+
validateSearch: zodValidator(productSearchSchema),
|
|
51
|
+
component: ProductsPage,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
function ProductsPage() {
|
|
55
|
+
// page: number, filter: string, sort: 'newest' | 'oldest' | 'price'
|
|
56
|
+
// ALL INFERRED — do not annotate
|
|
57
|
+
const { page, filter, sort } = Route.useSearch()
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div>
|
|
61
|
+
<p>
|
|
62
|
+
Page {page}, filter: {filter}, sort: {sort}
|
|
63
|
+
</p>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Reading Search Params
|
|
70
|
+
|
|
71
|
+
### In Route Components: `Route.useSearch()`
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
function ProductsPage() {
|
|
75
|
+
const { page, sort } = Route.useSearch()
|
|
76
|
+
return <div>Page {page}</div>
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### In Code-Split Components: `getRouteApi()`
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { getRouteApi } from '@tanstack/react-router'
|
|
84
|
+
|
|
85
|
+
const routeApi = getRouteApi('/products')
|
|
86
|
+
|
|
87
|
+
function ProductFilters() {
|
|
88
|
+
const { sort } = routeApi.useSearch()
|
|
89
|
+
return <select value={sort}>{/* options */}</select>
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### From Any Component: `useSearch({ from })`
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { useSearch } from '@tanstack/react-router'
|
|
97
|
+
|
|
98
|
+
function SortIndicator() {
|
|
99
|
+
const { sort } = useSearch({ from: '/products' })
|
|
100
|
+
return <span>Sorted by: {sort}</span>
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Loose Access: `useSearch({ strict: false })`
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
function GenericPaginator() {
|
|
108
|
+
const search = useSearch({ strict: false })
|
|
109
|
+
// search.page is number | undefined (union of all routes)
|
|
110
|
+
return <span>Page: {search.page ?? 1}</span>
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Writing Search Params
|
|
115
|
+
|
|
116
|
+
### Link with Function Form (Preserves Existing Params)
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { Link } from '@tanstack/react-router'
|
|
120
|
+
|
|
121
|
+
function Pagination() {
|
|
122
|
+
return (
|
|
123
|
+
<Link
|
|
124
|
+
from="/products"
|
|
125
|
+
search={(prev) => ({ ...prev, page: prev.page + 1 })}
|
|
126
|
+
>
|
|
127
|
+
Next Page
|
|
128
|
+
</Link>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Link with Object Form (Replaces All Params)
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<Link to="/products" search={{ page: 1, filter: '', sort: 'newest' }}>
|
|
137
|
+
Reset
|
|
138
|
+
</Link>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Programmatic: `useNavigate()`
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { useNavigate } from '@tanstack/react-router'
|
|
145
|
+
|
|
146
|
+
function SortDropdown() {
|
|
147
|
+
const navigate = useNavigate({ from: '/products' })
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<select
|
|
151
|
+
onChange={(e) => {
|
|
152
|
+
navigate({
|
|
153
|
+
search: (prev) => ({ ...prev, sort: e.target.value, page: 1 }),
|
|
154
|
+
})
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
<option value="newest">Newest</option>
|
|
158
|
+
<option value="price">Price</option>
|
|
159
|
+
</select>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Search Param Inheritance
|
|
165
|
+
|
|
166
|
+
Parent route search params are automatically merged into child routes:
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
// src/routes/shop.tsx — parent defines shared params
|
|
170
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
171
|
+
import { zodValidator, fallback } from '@tanstack/zod-adapter'
|
|
172
|
+
import { z } from 'zod'
|
|
173
|
+
|
|
174
|
+
const shopSearchSchema = z.object({
|
|
175
|
+
currency: fallback(z.enum(['USD', 'EUR']), 'USD').default('USD'),
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
export const Route = createFileRoute('/shop')({
|
|
179
|
+
validateSearch: zodValidator(shopSearchSchema),
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
// src/routes/shop/products.tsx — child inherits currency
|
|
185
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
186
|
+
|
|
187
|
+
export const Route = createFileRoute('/shop/products')({
|
|
188
|
+
component: ShopProducts,
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
function ShopProducts() {
|
|
192
|
+
// currency is available here from parent — fully typed
|
|
193
|
+
const { currency } = Route.useSearch()
|
|
194
|
+
return <div>Currency: {currency}</div>
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Search Middlewares
|
|
199
|
+
|
|
200
|
+
### `retainSearchParams` — Keep Params Across Navigation
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
import { createRootRoute, retainSearchParams } from '@tanstack/react-router'
|
|
204
|
+
import { zodValidator } from '@tanstack/zod-adapter'
|
|
205
|
+
import { z } from 'zod'
|
|
206
|
+
|
|
207
|
+
const rootSearchSchema = z.object({
|
|
208
|
+
debug: z.boolean().optional(),
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
export const Route = createRootRoute({
|
|
212
|
+
validateSearch: zodValidator(rootSearchSchema),
|
|
213
|
+
search: {
|
|
214
|
+
middlewares: [retainSearchParams(['debug'])],
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### `stripSearchParams` — Remove Default Values from URL
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
import { createFileRoute, stripSearchParams } from '@tanstack/react-router'
|
|
223
|
+
import { zodValidator } from '@tanstack/zod-adapter'
|
|
224
|
+
import { z } from 'zod'
|
|
225
|
+
|
|
226
|
+
const defaults = { sort: 'newest', page: 1 }
|
|
227
|
+
|
|
228
|
+
const searchSchema = z.object({
|
|
229
|
+
sort: z.string().default(defaults.sort),
|
|
230
|
+
page: z.number().default(defaults.page),
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
export const Route = createFileRoute('/items')({
|
|
234
|
+
validateSearch: zodValidator(searchSchema),
|
|
235
|
+
search: {
|
|
236
|
+
middlewares: [stripSearchParams(defaults)],
|
|
237
|
+
},
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Chaining Middlewares
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
export const Route = createFileRoute('/search')({
|
|
245
|
+
validateSearch: zodValidator(
|
|
246
|
+
z.object({
|
|
247
|
+
retainMe: z.string().optional(),
|
|
248
|
+
arrayWithDefaults: z.string().array().default(['foo', 'bar']),
|
|
249
|
+
required: z.string(),
|
|
250
|
+
}),
|
|
251
|
+
),
|
|
252
|
+
search: {
|
|
253
|
+
middlewares: [
|
|
254
|
+
retainSearchParams(['retainMe']),
|
|
255
|
+
stripSearchParams({ arrayWithDefaults: ['foo', 'bar'] }),
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Custom Serialization
|
|
262
|
+
|
|
263
|
+
Override the default JSON serialization at the router level:
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
import {
|
|
267
|
+
createRouter,
|
|
268
|
+
parseSearchWith,
|
|
269
|
+
stringifySearchWith,
|
|
270
|
+
} from '@tanstack/react-router'
|
|
271
|
+
|
|
272
|
+
const router = createRouter({
|
|
273
|
+
routeTree,
|
|
274
|
+
// Example: use JSURL2 for compact, human-readable URLs
|
|
275
|
+
parseSearch: parseSearchWith(parse),
|
|
276
|
+
stringifySearch: stringifySearchWith(stringify),
|
|
277
|
+
})
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Using Search Params in Loaders via `loaderDeps`
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
export const Route = createFileRoute('/products')({
|
|
284
|
+
validateSearch: zodValidator(productSearchSchema),
|
|
285
|
+
// Pick ONLY the params the loader needs — not the entire search object
|
|
286
|
+
loaderDeps: ({ search }) => ({ page: search.page }),
|
|
287
|
+
loader: async ({ deps }) => {
|
|
288
|
+
return fetchProducts({ page: deps.page })
|
|
289
|
+
},
|
|
290
|
+
})
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Common Mistakes
|
|
294
|
+
|
|
295
|
+
### 1. HIGH: Using zod `.catch()` with `zodValidator()` instead of adapter `fallback()`
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
// WRONG — .catch() with zodValidator makes the type unknown
|
|
299
|
+
const schema = z.object({ page: z.number().catch(1) })
|
|
300
|
+
validateSearch: zodValidator(schema) // page is typed as unknown!
|
|
301
|
+
|
|
302
|
+
// CORRECT — fallback() preserves the inferred type
|
|
303
|
+
import { fallback } from '@tanstack/zod-adapter'
|
|
304
|
+
const schema = z.object({ page: fallback(z.number(), 1) })
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### 2. HIGH: Returning entire search object from `loaderDeps`
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
// WRONG — loader re-runs on ANY search param change
|
|
311
|
+
loaderDeps: ({ search }) => search
|
|
312
|
+
|
|
313
|
+
// CORRECT — loader only re-runs when page changes
|
|
314
|
+
loaderDeps: ({ search }) => ({ page: search.page })
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### 3. HIGH: Passing Date objects in search params
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
// WRONG — Date does not serialize correctly to JSON in URLs
|
|
321
|
+
<Link search={{ startDate: new Date() }}>
|
|
322
|
+
|
|
323
|
+
// CORRECT — convert to ISO string
|
|
324
|
+
<Link search={{ startDate: new Date().toISOString() }}>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 4. MEDIUM: Parent route missing `validateSearch` blocks inheritance
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
// WRONG — child cannot access shared params
|
|
331
|
+
export const Route = createRootRoute({
|
|
332
|
+
component: RootComponent,
|
|
333
|
+
// no validateSearch!
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
// CORRECT — parent must define validateSearch for children to inherit
|
|
337
|
+
export const Route = createRootRoute({
|
|
338
|
+
validateSearch: zodValidator(globalSearchSchema),
|
|
339
|
+
component: RootComponent,
|
|
340
|
+
})
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### 5. HIGH (cross-skill): Using search as object instead of function loses params
|
|
344
|
+
|
|
345
|
+
```tsx
|
|
346
|
+
// WRONG — replaces ALL search params, losing any existing ones
|
|
347
|
+
<Link to="." search={{ page: 2 }}>Page 2</Link>
|
|
348
|
+
|
|
349
|
+
// CORRECT — preserves existing params, updates only page
|
|
350
|
+
<Link to="." search={(prev) => ({ ...prev, page: 2 })}>Page 2</Link>
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## References
|
|
354
|
+
|
|
355
|
+
- [Validation Patterns Reference](./references/validation-patterns.md) — comprehensive patterns for all validation libraries
|