@@ -28,7 +28,7 @@ export const Route = createFileRoute('/products')({
28
28
29
29
function ProductsPage() {
30
30
const { page, category, showSale } = Route .useSearch ()
31
-
31
+
32
32
return (
33
33
<div >
34
34
<h1 >Products</h1 >
@@ -43,6 +43,7 @@ function ProductsPage() {
43
43
## Why Use Schema Validation for Search Parameters?
44
44
45
45
** Production Benefits:**
46
+
46
47
- ** Type Safety** : Automatic TypeScript inference
47
48
- ** Runtime Validation** : Catches invalid URL parameters gracefully
48
49
- ** Default Values** : Fallback handling for missing parameters
@@ -152,16 +153,16 @@ const shopSearchSchema = z.object({
152
153
// Pagination
153
154
page: fallback (z .number (), 1 ).default (1 ),
154
155
limit: fallback (z .number (), 20 ).default (20 ),
155
-
156
+
156
157
// Filtering
157
158
category: fallback (z .string (), ' all' ).default (' all' ),
158
159
minPrice: fallback (z .number (), 0 ).default (0 ),
159
160
maxPrice: fallback (z .number (), 1000 ).default (1000 ),
160
-
161
+
161
162
// Settings
162
163
sort: fallback (z .enum ([' name' , ' price' , ' date' ]), ' name' ).default (' name' ),
163
164
ascending: fallback (z .boolean (), true ).default (true ),
164
-
165
+
165
166
// Optional parameters
166
167
searchTerm: z .string ().optional (),
167
168
showOnlyInStock: fallback (z .boolean (), false ).default (false ),
@@ -190,15 +191,25 @@ Use the route's `useSearch()` hook to access validated and typed search paramete
190
191
``` tsx
191
192
function ShopPage() {
192
193
const searchParams = Route .useSearch ()
193
-
194
+
194
195
// All properties are fully type-safe and validated
195
- const { page, limit, category, sort, ascending, searchTerm, showOnlyInStock } = searchParams
196
-
196
+ const {
197
+ page,
198
+ limit,
199
+ category,
200
+ sort,
201
+ ascending,
202
+ searchTerm,
203
+ showOnlyInStock,
204
+ } = searchParams
205
+
197
206
return (
198
207
<div >
199
208
<h1 >Shop - Page { page } </h1 >
200
209
<div >Category: { category } </div >
201
- <div >Sort: { sort } ({ ascending ? ' ascending' : ' descending' } )</div >
210
+ <div >
211
+ Sort: { sort } ({ ascending ? ' ascending' : ' descending' } )
212
+ </div >
202
213
<div >Items per page: { limit } </div >
203
214
<div >In stock only: { showOnlyInStock ? ' Yes' : ' No' } </div >
204
215
{ searchTerm && <div >Search: "{ searchTerm } "</div >}
@@ -224,10 +235,10 @@ export const Route = createFileRoute('/posts')({
224
235
225
236
function PostsPage() {
226
237
const { page, limit } = Route .useSearch ()
227
-
238
+
228
239
// Calculate offset for API calls
229
240
const offset = (page - 1 ) * limit
230
-
241
+
231
242
return (
232
243
<div >
233
244
<h1 >Posts (Page { page } )</h1 >
@@ -243,13 +254,10 @@ function PostsPage() {
243
254
244
255
``` tsx
245
256
const catalogSchema = z .object ({
246
- sort: fallback (
247
- z .enum ([' name' , ' date' , ' price' ]),
248
- ' name'
249
- ).default (' name' ),
257
+ sort: fallback (z .enum ([' name' , ' date' , ' price' ]), ' name' ).default (' name' ),
250
258
category: fallback (
251
- z .enum ([' electronics' , ' clothing' , ' books' , ' all' ]),
252
- ' all'
259
+ z .enum ([' electronics' , ' clothing' , ' books' , ' all' ]),
260
+ ' all' ,
253
261
).default (' all' ),
254
262
ascending: fallback (z .boolean (), true ).default (true ),
255
263
})
@@ -266,23 +274,25 @@ export const Route = createFileRoute('/catalog')({
266
274
const dashboardSchema = z .object ({
267
275
// Numbers with validation
268
276
userId: fallback (z .number ().positive (), 1 ).default (1 ),
269
- refreshInterval: fallback (z .number ().min (1000 ).max (60000 ), 5000 ).default (5000 ),
270
-
277
+ refreshInterval: fallback (z .number ().min (1000 ).max (60000 ), 5000 ).default (
278
+ 5000 ,
279
+ ),
280
+
271
281
// Strings with validation
272
282
theme: fallback (z .enum ([' light' , ' dark' ]), ' light' ).default (' light' ),
273
283
timezone: z .string ().optional (),
274
-
284
+
275
285
// Arrays with validation
276
286
selectedIds: fallback (z .number ().array (), []).default ([]),
277
287
tags: fallback (z .string ().array (), []).default ([]),
278
-
288
+
279
289
// Objects with validation
280
290
filters: fallback (
281
291
z .object ({
282
292
status: z .enum ([' active' , ' inactive' ]).optional (),
283
293
type: z .string ().optional (),
284
- }),
285
- {}
294
+ }),
295
+ {},
286
296
).default ({}),
287
297
})
288
298
```
@@ -312,7 +322,7 @@ const routeApi = getRouteApi('/products')
312
322
313
323
export function ProductFilters() {
314
324
const { category, sort, showSale } = routeApi .useSearch ()
315
-
325
+
316
326
return (
317
327
<div >
318
328
<select value = { category } >
@@ -333,12 +343,8 @@ import { useSearch } from '@tanstack/react-router'
333
343
334
344
function GenericSearchDisplay() {
335
345
const search = useSearch ({ from: ' /products' })
336
-
337
- return (
338
- <div >
339
- Current filters: { JSON .stringify (search , null , 2 )}
340
- </div >
341
- )
346
+
347
+ return <div >Current filters: { JSON .stringify (search , null , 2 )} </div >
342
348
}
343
349
```
344
350
@@ -352,15 +358,15 @@ export const Route = createFileRoute('/example')({
352
358
validateSearch : (search : Record <string , unknown >) => ({
353
359
// Numbers need coercion from URL strings
354
360
page: Number (search .page ) || 1 ,
355
-
361
+
356
362
// Strings can be cast with defaults
357
363
category: (search .category as string ) || ' all' ,
358
-
364
+
359
365
// Booleans: TanStack Router auto-converts "true"/"false" to booleans
360
366
showSale: Boolean (search .showSale ),
361
-
367
+
362
368
// Arrays need JSON parsing validation
363
- selectedIds: Array .isArray (search .selectedIds )
369
+ selectedIds: Array .isArray (search .selectedIds )
364
370
? search .selectedIds .map (Number ).filter (Boolean )
365
371
: [],
366
372
}),
@@ -432,7 +438,7 @@ const schema = z.object({
432
438
const schema = z .object ({
433
439
// Required with default (navigation can omit, but always present in component)
434
440
page: fallback (z .number (), 1 ).default (1 ),
435
-
441
+
436
442
// Truly optional (can be undefined in component)
437
443
searchTerm: z .string ().optional (),
438
444
})
@@ -450,12 +456,14 @@ const schema = z.object({
450
456
z .object ({
451
457
status: z .enum ([' active' , ' inactive' ]).optional (),
452
458
tags: z .string ().array ().optional (),
453
- dateRange: z .object ({
454
- start: z .string ().pipe (z .coerce .date ()),
455
- end: z .string ().pipe (z .coerce .date ()),
456
- }).optional (),
459
+ dateRange: z
460
+ .object ({
461
+ start: z .string ().pipe (z .coerce .date ()),
462
+ end: z .string ().pipe (z .coerce .date ()),
463
+ })
464
+ .optional (),
457
465
}),
458
- {}
466
+ {},
459
467
).default ({}),
460
468
})
461
469
```
@@ -477,4 +485,4 @@ const schema = z.object({
477
485
- [ TanStack Zod Adapter] ( https://tanstack.com/router/latest/docs/framework/react/api/router/zodValidator ) - Official Zod adapter
478
486
- [ TanStack Valibot Adapter] ( https://tanstack.com/router/latest/docs/framework/react/api/router/valibotValidator ) - Official Valibot adapter
479
487
- [ Search Parameters Guide] ( ../guide/search-params.md ) - Comprehensive search parameters documentation
480
- - [ Type Safety Guide] ( ../guide/type-safety.md ) - Understanding TanStack Router's type safety
488
+ - [ Type Safety Guide] ( ../guide/type-safety.md ) - Understanding TanStack Router's type safety
0 commit comments