@hitch42/applet 0.1.0-beta.9 → 0.1.1-beta.10
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/dist/_internal/client.js +120 -11
- package/dist/_internal/log-context.d.ts +34 -0
- package/dist/_internal/log-context.js +2 -0
- package/dist/_internal/server.js +112 -57
- package/dist/applet.js +12 -12
- package/dist/build-Bh_S5BUv.js +2 -0
- package/dist/{build-CAb26kMY.js → build-CEfFoIZZ.js} +1 -1
- package/dist/{ci-5qWl7lT3.js → ci-BKw14X2h.js} +14 -13
- package/dist/client.d.ts +2 -2
- package/dist/{contract-rP_eV9PK.d.ts → contract-CsUpTBe0.d.ts} +1 -1
- package/dist/{deploy-D_OFJ6-Q.js → deploy-wGYjwXrx.js} +139 -88
- package/dist/{generate-CBPnHoqX.js → generate-BEIYs17x.js} +54 -22
- package/dist/generate-Dox-OYt6.js +2 -0
- package/dist/headers-DkNr9MLJ.js +17 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/{init-tfzwuYnk.js → init-6oGPM46R.js} +10 -15
- package/dist/{load-config-BYSxDVAl.js → load-config-xEdTXs0y.js} +2 -2
- package/dist/log-context-zu8U9Sjm.js +73 -0
- package/dist/{login-C7RKmmTe.js → login-CUx3TmFQ.js} +1 -1
- package/dist/{logs-DgjgAxj2.js → logs-DSjuEN0Q.js} +20 -22
- package/dist/{manifest-DD_SGkS6.d.ts → manifest-C6_F4bL8.d.ts} +5 -1
- package/dist/{manifest-Bp87Uz0a.js → manifest-CJSL9GuC.js} +20 -4
- package/dist/{project-D6XHllmC.js → project-BRZGd6xq.js} +197 -16
- package/dist/query-cache-CXmAyLv3.js +82 -0
- package/dist/react.d.ts +2 -1
- package/dist/react.js +59 -39
- package/dist/{schedules-dhU1yyFr.js → schedules-C8_Svr4h.js} +1 -1
- package/dist/server.d.ts +8 -3
- package/dist/server.js +29 -8
- package/dist/telephony.d.ts +24 -4
- package/dist/telephony.js +135 -16
- package/dist/{variables-Bwnr7oxf.js → variables-DT5RyHdH.js} +13 -13
- package/dist/vite.d.ts +3 -60
- package/dist/vite.js +50 -15
- package/dist/{workflow-engine-MogejNfF.js → workflow-engine-BOTzURKM.js} +1 -7
- package/dist/{workspace-XVtM12ZC.js → workspace-D0dnnlxL.js} +33 -38
- package/package.json +13 -6
- package/scaffold/.agents/skills/hitch-applets/SKILL.md +29 -3
- package/scaffold/.agents/skills/hitch-applets/references/manifest.md +8 -0
- package/scaffold/.agents/skills/hitch-applets/references/markdown.md +2 -0
- package/scaffold/.agents/skills/hitch-applets/references/records.md +2 -0
- package/scaffold/.agents/skills/hitch-data/SKILL.md +2 -1
- package/scaffold/.agents/skills/hitch-data/references/attributes.md +7 -1
- package/scaffold/.agents/skills/hitch-data/references/limits.md +13 -13
- package/scaffold/.agents/skills/hitch-theory/SKILL.md +3 -2
- package/scaffold/.agents/skills/react-best-practices/SKILL.md +127 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-effect-event-deps.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-init-once.md +42 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-defer-await.md +82 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-dependencies.md +48 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-parallel.md +24 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-analyzable-paths.md +42 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-barrel-imports.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-conditional.md +35 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-defer-third-party.md +46 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-dynamic-imports.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-preload.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-event-listeners.md +78 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-localstorage-schema.md +74 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-batch-dom-css.md +110 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-function-results.md +58 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-storage.md +56 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-flatmap-filter.md +51 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-length-check-first.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-request-idle-callback.md +104 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-conditional-render.md +32 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-hoist-jsx.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-resource-hints.md +81 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-script-defer-async.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-usetransition-loading.md +43 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-functional-setstate.md +77 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-lazy-state-init.md +56 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-memo-with-default-value.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-no-inline-components.md +75 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-use-deferred-value.md +59 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/scaffold/.agents/skills/tanstack-query/SKILL.md +136 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/automatic-invalidation-after-mutations.md +124 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/broadcast-realtime-and-multi-tab-synchronization.md +150 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/compose-query-with-tanstack-router-and-start.md +248 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/enforce-query-best-practices-with-eslint.md +167 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/persist-offline-and-restore-caches.md +181 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/query-data-and-forms.md +156 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/build-query-abstractions.md +130 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/cancel-queries-and-consume-abort-signals.md +176 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/concurrent-optimistic-updates.md +135 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/coordinate-query-execution.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/design-query-keys-and-options.md +182 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/fetch-and-observe-queries.md +193 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/implement-optimistic-updates-and-cache-writes.md +168 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/paginate-and-build-infinite-queries.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/seed-placeholder-select-and-transform-data.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/selectors-and-derived-state.md +145 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/tune-defaults-freshness-retries-and-refetching.md +172 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/understand-query-internals-and-observers.md +104 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/write-mutations-and-invalidate-related-queries.md +183 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/debug-with-devtools.md +169 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/handle-status-and-errors.md +119 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/shape-data-and-render-efficiently.md +184 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/use-framework-adapter-reactivity.md +185 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/use-suspense-and-error-boundaries.md +208 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/migrate-major-versions-and-codemods.md +169 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/prefetch-and-remove-request-waterfalls.md +228 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/setup-query-client-and-providers.md +161 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/ssr-hydration-and-streaming.md +209 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/test-query-code.md +160 -0
- package/scaffold/.agents/skills/tanstack-router/SKILL.md +134 -0
- package/scaffold/.agents/skills/tanstack-router/references/compositions/router-query.md +412 -0
- package/scaffold/.agents/skills/tanstack-router/references/react-router.md +495 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/auth-and-guards.md +495 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/code-splitting.md +318 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/data-loading.md +490 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/index.md +158 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/navigation.md +433 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/not-found-and-errors.md +436 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/path-params.md +383 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/search-params.md +343 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/ssr.md +462 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/type-safety.md +493 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-plugin.md +250 -0
- package/scaffold/.agents/skills/tanstack-router/references/virtual-file-routes.md +215 -0
- package/scaffold/.agents/skills/web-design-guidelines/SKILL.md +184 -0
- package/scaffold/.github/workflows/hitch.yml +1 -1
- package/scaffold/AGENTS.md +2 -2
- package/scaffold/applet/src/client/components/ui/alert.tsx +1 -1
- package/scaffold/applet/src/client/components/ui/button.tsx +1 -1
- package/scaffold/applet/src/client/components/ui/checkbox.tsx +1 -1
- package/scaffold/applet/src/client/components/ui/dialog.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/dropdown-menu.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/input-group.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/select.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/sheet.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/spinner.tsx +2 -1
- package/scaffold/applet/src/client/components/ui/tabs.tsx +1 -1
- package/scaffold/applet/src/client/lib/utils.ts +1 -6
- package/scaffold/applet/src/client/styles/globals.css +2 -0
- package/dist/build-BfbTbt_r.js +0 -2
- package/dist/generate-BgepN4-b.js +0 -2
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: path-params
|
|
3
|
+
description: >-
|
|
4
|
+
Dynamic path segments ($paramName), splat routes ($ / _splat),
|
|
5
|
+
optional params ({-$paramName}), prefix/suffix patterns ({$param}.ext),
|
|
6
|
+
useParams, params.parse/stringify, pathParamsAllowedCharacters,
|
|
7
|
+
i18n locale patterns.
|
|
8
|
+
metadata:
|
|
9
|
+
type: sub-skill
|
|
10
|
+
library: tanstack-router
|
|
11
|
+
library_version: "1.171.15"
|
|
12
|
+
requires:
|
|
13
|
+
- router-core
|
|
14
|
+
sources:
|
|
15
|
+
- TanStack/router:docs/router/guide/path-params.md
|
|
16
|
+
- TanStack/router:docs/router/routing/routing-concepts.md
|
|
17
|
+
- TanStack/router:docs/router/guide/internationalization-i18n.md
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Path Params
|
|
21
|
+
|
|
22
|
+
Path params capture dynamic URL segments into named variables. They are defined with a `$` prefix in the route path.
|
|
23
|
+
|
|
24
|
+
> **CRITICAL**: Never interpolate params into the `to` string. Always use the `params` prop. This is the most common agent mistake for path params.
|
|
25
|
+
|
|
26
|
+
> **CRITICAL**: Types are fully inferred. Never annotate the return of `useParams()`.
|
|
27
|
+
|
|
28
|
+
## Dynamic Segments
|
|
29
|
+
|
|
30
|
+
A segment prefixed with `$` captures text until the next `/`.
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// src/routes/posts.$postId.tsx
|
|
34
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
35
|
+
|
|
36
|
+
export const Route = createFileRoute("/posts/$postId")({
|
|
37
|
+
loader: async ({ params }) => {
|
|
38
|
+
// params.postId is string — fully inferred, do not annotate
|
|
39
|
+
return fetchPost(params.postId);
|
|
40
|
+
},
|
|
41
|
+
component: PostComponent,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function PostComponent() {
|
|
45
|
+
const { postId } = Route.useParams();
|
|
46
|
+
const data = Route.useLoaderData();
|
|
47
|
+
return (
|
|
48
|
+
<h1>
|
|
49
|
+
Post {postId}: {data.title}
|
|
50
|
+
</h1>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Multiple dynamic segments work across path levels:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// src/routes/teams.$teamId.members.$memberId.tsx
|
|
59
|
+
export const Route = createFileRoute("/teams/$teamId/members/$memberId")({
|
|
60
|
+
component: MemberComponent,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function MemberComponent() {
|
|
64
|
+
const { teamId, memberId } = Route.useParams();
|
|
65
|
+
return (
|
|
66
|
+
<div>
|
|
67
|
+
Team {teamId}, Member {memberId}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Splat / Catch-All Routes
|
|
74
|
+
|
|
75
|
+
A route with a path ending in `$` (bare dollar sign) captures everything after it. The value is available under the `_splat` key.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// src/routes/files.$.tsx
|
|
79
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
80
|
+
|
|
81
|
+
export const Route = createFileRoute("/files/$")({
|
|
82
|
+
component: FileViewer,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
function FileViewer() {
|
|
86
|
+
const { _splat } = Route.useParams();
|
|
87
|
+
// URL: /files/documents/report.pdf → _splat = "documents/report.pdf"
|
|
88
|
+
return <div>File path: {_splat}</div>;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Optional Params
|
|
93
|
+
|
|
94
|
+
Optional params use `{-$paramName}` syntax. The segment may or may not be present. When absent, the value is `undefined`.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
// src/routes/posts.{-$category}.tsx
|
|
98
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
99
|
+
|
|
100
|
+
export const Route = createFileRoute("/posts/{-$category}")({
|
|
101
|
+
component: PostsComponent,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function PostsComponent() {
|
|
105
|
+
const { category } = Route.useParams();
|
|
106
|
+
// URL: /posts → category is undefined
|
|
107
|
+
// URL: /posts/tech → category is "tech"
|
|
108
|
+
return <div>{category ? `Posts in ${category}` : "All Posts"}</div>;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Multiple optional params:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
// Matches: /posts, /posts/tech, /posts/tech/hello-world
|
|
116
|
+
export const Route = createFileRoute("/posts/{-$category}/{-$slug}")({
|
|
117
|
+
component: PostComponent,
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### i18n with Optional Locale
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
// src/routes/{-$locale}/about.tsx
|
|
125
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
126
|
+
|
|
127
|
+
export const Route = createFileRoute("/{-$locale}/about")({
|
|
128
|
+
component: AboutComponent,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
function AboutComponent() {
|
|
132
|
+
const { locale } = Route.useParams();
|
|
133
|
+
const currentLocale = locale || "en";
|
|
134
|
+
return <h1>{currentLocale === "fr" ? "À Propos" : "About Us"}</h1>;
|
|
135
|
+
}
|
|
136
|
+
// Matches: /about, /en/about, /fr/about
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Prefix and Suffix Patterns
|
|
140
|
+
|
|
141
|
+
Curly braces `{}` around `$paramName` allow text before or after the dynamic part within a single segment.
|
|
142
|
+
|
|
143
|
+
### Prefix
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
// src/routes/posts/post-{$postId}.tsx
|
|
147
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
148
|
+
|
|
149
|
+
export const Route = createFileRoute("/posts/post-{$postId}")({
|
|
150
|
+
component: PostComponent,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
function PostComponent() {
|
|
154
|
+
const { postId } = Route.useParams();
|
|
155
|
+
// URL: /posts/post-123 → postId = "123"
|
|
156
|
+
return <div>Post ID: {postId}</div>;
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Suffix
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
// src/routes/files/{$fileName}[.]txt.tsx
|
|
164
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
165
|
+
|
|
166
|
+
export const Route = createFileRoute("/files/{$fileName}.txt")({
|
|
167
|
+
component: FileComponent,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
function FileComponent() {
|
|
171
|
+
const { fileName } = Route.useParams();
|
|
172
|
+
// URL: /files/readme.txt → fileName = "readme"
|
|
173
|
+
return <div>File: {fileName}.txt</div>;
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Combined Prefix + Suffix
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
// URL: /users/user-456.json → userId = "456"
|
|
181
|
+
export const Route = createFileRoute("/users/user-{$userId}.json")({
|
|
182
|
+
component: UserComponent,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
function UserComponent() {
|
|
186
|
+
const { userId } = Route.useParams();
|
|
187
|
+
return <div>User: {userId}</div>;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Navigating with Path Params
|
|
192
|
+
|
|
193
|
+
### Object Form
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
import { Link } from "@tanstack/react-router";
|
|
197
|
+
|
|
198
|
+
function PostLink({ postId }: { postId: string }) {
|
|
199
|
+
return (
|
|
200
|
+
<Link to="/posts/$postId" params={{ postId }}>
|
|
201
|
+
View Post
|
|
202
|
+
</Link>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Function Form (Preserves Other Params)
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
function PostLink({ postId }: { postId: string }) {
|
|
211
|
+
return (
|
|
212
|
+
<Link to="/posts/$postId" params={(prev) => ({ ...prev, postId })}>
|
|
213
|
+
View Post
|
|
214
|
+
</Link>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Programmatic Navigation
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
223
|
+
|
|
224
|
+
function GoToPost({ postId }: { postId: string }) {
|
|
225
|
+
const navigate = useNavigate();
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<button
|
|
229
|
+
onClick={() => {
|
|
230
|
+
navigate({ to: "/posts/$postId", params: { postId } });
|
|
231
|
+
}}
|
|
232
|
+
>
|
|
233
|
+
Go to Post
|
|
234
|
+
</button>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Navigating with Optional Params
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
// Include the optional param
|
|
243
|
+
<Link to="/posts/{-$category}" params={{ category: 'tech' }}>
|
|
244
|
+
Tech Posts
|
|
245
|
+
</Link>
|
|
246
|
+
|
|
247
|
+
// Omit the optional param (renders /posts)
|
|
248
|
+
<Link to="/posts/{-$category}" params={{ category: undefined }}>
|
|
249
|
+
All Posts
|
|
250
|
+
</Link>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Reading Params Outside Route Components
|
|
254
|
+
|
|
255
|
+
### `useParams` with `from`
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
import { useParams } from "@tanstack/react-router";
|
|
259
|
+
|
|
260
|
+
function PostHeader() {
|
|
261
|
+
const { postId } = useParams({ from: "/posts/$postId" });
|
|
262
|
+
return <h2>Post {postId}</h2>;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### `useParams` with `strict: false`
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
function GenericBreadcrumb() {
|
|
270
|
+
const params = useParams({ strict: false });
|
|
271
|
+
// params is a union of all possible route params
|
|
272
|
+
return <span>{params.postId ?? "Home"}</span>;
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Params in Loaders and `beforeLoad`
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
export const Route = createFileRoute("/posts/$postId")({
|
|
280
|
+
beforeLoad: async ({ params }) => {
|
|
281
|
+
// params.postId available here
|
|
282
|
+
const canView = await checkPermission(params.postId);
|
|
283
|
+
if (!canView) throw redirect({ to: "/unauthorized" });
|
|
284
|
+
},
|
|
285
|
+
loader: async ({ params }) => {
|
|
286
|
+
return fetchPost(params.postId);
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Allowed Characters
|
|
292
|
+
|
|
293
|
+
By default, params are encoded with `encodeURIComponent`. Allow extra characters via router config:
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
import { createRouter } from "@tanstack/react-router";
|
|
297
|
+
|
|
298
|
+
const router = createRouter({
|
|
299
|
+
routeTree,
|
|
300
|
+
pathParamsAllowedCharacters: ["@", "+"],
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Allowed characters: `;`, `:`, `@`, `&`, `=`, `+`, `$`, `,`.
|
|
305
|
+
|
|
306
|
+
## Common Mistakes
|
|
307
|
+
|
|
308
|
+
### 1. CRITICAL (cross-skill): Interpolating path params into `to` string
|
|
309
|
+
|
|
310
|
+
```tsx
|
|
311
|
+
// WRONG — breaks type safety and param encoding
|
|
312
|
+
<Link to={`/posts/${postId}`}>Post</Link>
|
|
313
|
+
|
|
314
|
+
// CORRECT — use params prop
|
|
315
|
+
<Link to="/posts/$postId" params={{ postId }}>Post</Link>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### 2. MEDIUM: Using `*` for splat routes instead of `$`
|
|
319
|
+
|
|
320
|
+
TanStack Router uses `$` for splat routes. The captured value is under `_splat`, not `*`.
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
// WRONG (React Router / other frameworks)
|
|
324
|
+
// <Route path="/files/*" />
|
|
325
|
+
|
|
326
|
+
// CORRECT (TanStack Router)
|
|
327
|
+
// File: src/routes/files.$.tsx
|
|
328
|
+
export const Route = createFileRoute("/files/$")({
|
|
329
|
+
component: () => {
|
|
330
|
+
const { _splat } = Route.useParams();
|
|
331
|
+
return <div>{_splat}</div>;
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
> Note: `*` works in v1 for backwards compatibility but will be removed in v2. Always use `_splat`.
|
|
337
|
+
|
|
338
|
+
### 3. MEDIUM: Using curly braces for basic dynamic segments
|
|
339
|
+
|
|
340
|
+
Curly braces are ONLY for prefix/suffix patterns and optional params. Basic dynamic segments use bare `$`.
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
// WRONG — braces not needed for basic params
|
|
344
|
+
createFileRoute("/posts/{$postId}");
|
|
345
|
+
|
|
346
|
+
// CORRECT — bare $ for basic dynamic segments
|
|
347
|
+
createFileRoute("/posts/$postId");
|
|
348
|
+
|
|
349
|
+
// CORRECT — braces for prefix pattern
|
|
350
|
+
createFileRoute("/posts/post-{$postId}");
|
|
351
|
+
|
|
352
|
+
// CORRECT — braces for optional param
|
|
353
|
+
createFileRoute("/posts/{-$category}");
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### 4. Params are always strings
|
|
357
|
+
|
|
358
|
+
Path params are always parsed as strings. If you need a number, parse in the loader or component:
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
export const Route = createFileRoute("/posts/$postId")({
|
|
362
|
+
loader: async ({ params }) => {
|
|
363
|
+
const id = parseInt(params.postId, 10);
|
|
364
|
+
if (isNaN(id)) throw notFound();
|
|
365
|
+
return fetchPost(id);
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
You can also use `params.parse` and `params.stringify` on the route for bidirectional transformation:
|
|
371
|
+
|
|
372
|
+
```tsx
|
|
373
|
+
export const Route = createFileRoute("/posts/$postId")({
|
|
374
|
+
params: {
|
|
375
|
+
parse: (raw) => ({ postId: parseInt(raw.postId, 10) }),
|
|
376
|
+
stringify: (parsed) => ({ postId: String(parsed.postId) }),
|
|
377
|
+
},
|
|
378
|
+
loader: async ({ params }) => {
|
|
379
|
+
// params.postId is now number
|
|
380
|
+
return fetchPost(params.postId);
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
```
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 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
|
+
metadata:
|
|
9
|
+
type: sub-skill
|
|
10
|
+
library: tanstack-router
|
|
11
|
+
library_version: "1.171.15"
|
|
12
|
+
requires:
|
|
13
|
+
- router-core
|
|
14
|
+
sources:
|
|
15
|
+
- TanStack/router:docs/router/guide/search-params.md
|
|
16
|
+
- TanStack/router:docs/router/how-to/setup-basic-search-params.md
|
|
17
|
+
- TanStack/router:docs/router/how-to/validate-search-params.md
|
|
18
|
+
- TanStack/router:docs/router/how-to/navigate-with-search-params.md
|
|
19
|
+
- TanStack/router:docs/router/how-to/share-search-params-across-routes.md
|
|
20
|
+
- TanStack/router:docs/router/guide/custom-search-param-serialization.md
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Search Params
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
> **CRITICAL**: When using `zodValidator()` and Zod v3, 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). It also does not apply to Zod v4, which should use `.catch()` and not use the `zodValidator()`.
|
|
28
|
+
> **CRITICAL**: Types are fully inferred. Never annotate the return of `useSearch()`.
|
|
29
|
+
|
|
30
|
+
## Setup: Zod Adapter (Recommended)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install zod @tanstack/zod-adapter
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
// src/routes/products.tsx
|
|
38
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
39
|
+
import { z } from "zod";
|
|
40
|
+
|
|
41
|
+
const productSearchSchema = z.object({
|
|
42
|
+
page: z.number().default(1).catch(1),
|
|
43
|
+
filter: z.string().default(""),
|
|
44
|
+
sort: z.enum(["newest", "oldest", "price"]).default("newest").catch("newest"),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const Route = createFileRoute("/products")({
|
|
48
|
+
validateSearch: productSearchSchema,
|
|
49
|
+
component: ProductsPage,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function ProductsPage() {
|
|
53
|
+
// page: number, filter: string, sort: 'newest' | 'oldest' | 'price'
|
|
54
|
+
// ALL INFERRED — do not annotate
|
|
55
|
+
const { page, filter, sort } = Route.useSearch();
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div>
|
|
59
|
+
<p>
|
|
60
|
+
Page {page}, filter: {filter}, sort: {sort}
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Reading Search Params
|
|
68
|
+
|
|
69
|
+
### In Route Components: `Route.useSearch()`
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
function ProductsPage() {
|
|
73
|
+
const { page, sort } = Route.useSearch();
|
|
74
|
+
return <div>Page {page}</div>;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### In Code-Split Components: `getRouteApi()`
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { getRouteApi } from "@tanstack/react-router";
|
|
82
|
+
|
|
83
|
+
const routeApi = getRouteApi("/products");
|
|
84
|
+
|
|
85
|
+
function ProductFilters() {
|
|
86
|
+
const { sort } = routeApi.useSearch();
|
|
87
|
+
return <select value={sort}>{/* options */}</select>;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### From Any Component: `useSearch({ from })`
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import { useSearch } from "@tanstack/react-router";
|
|
95
|
+
|
|
96
|
+
function SortIndicator() {
|
|
97
|
+
const { sort } = useSearch({ from: "/products" });
|
|
98
|
+
return <span>Sorted by: {sort}</span>;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Loose Access: `useSearch({ strict: false })`
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
function GenericPaginator() {
|
|
106
|
+
const search = useSearch({ strict: false });
|
|
107
|
+
// search.page is number | undefined (union of all routes)
|
|
108
|
+
return <span>Page: {search.page ?? 1}</span>;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Writing Search Params
|
|
113
|
+
|
|
114
|
+
### Link with Function Form (Preserves Existing Params)
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import { Link } from "@tanstack/react-router";
|
|
118
|
+
|
|
119
|
+
function Pagination() {
|
|
120
|
+
return (
|
|
121
|
+
<Link from="/products" search={(prev) => ({ ...prev, page: prev.page + 1 })}>
|
|
122
|
+
Next Page
|
|
123
|
+
</Link>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Link with Object Form (Replaces All Params)
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
<Link to="/products" search={{ page: 1, filter: "", sort: "newest" }}>
|
|
132
|
+
Reset
|
|
133
|
+
</Link>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Programmatic: `useNavigate()`
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
140
|
+
|
|
141
|
+
function SortDropdown() {
|
|
142
|
+
const navigate = useNavigate({ from: "/products" });
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<select
|
|
146
|
+
onChange={(e) => {
|
|
147
|
+
navigate({
|
|
148
|
+
search: (prev) => ({ ...prev, sort: e.target.value, page: 1 }),
|
|
149
|
+
});
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
<option value="newest">Newest</option>
|
|
153
|
+
<option value="price">Price</option>
|
|
154
|
+
</select>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Search Param Inheritance
|
|
160
|
+
|
|
161
|
+
Parent route search params are automatically merged into child routes:
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
// src/routes/shop.tsx — parent defines shared params
|
|
165
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
166
|
+
import { z } from "zod";
|
|
167
|
+
|
|
168
|
+
const shopSearchSchema = z.object({
|
|
169
|
+
currency: z.enum(["USD", "EUR"]).default("USD").catch("USD"),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export const Route = createFileRoute("/shop")({
|
|
173
|
+
validateSearch: shopSearchSchema,
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
// src/routes/shop/products.tsx — child inherits currency
|
|
179
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
180
|
+
|
|
181
|
+
export const Route = createFileRoute("/shop/products")({
|
|
182
|
+
component: ShopProducts,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
function ShopProducts() {
|
|
186
|
+
// currency is available here from parent — fully typed
|
|
187
|
+
const { currency } = Route.useSearch();
|
|
188
|
+
return <div>Currency: {currency}</div>;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Search Middlewares
|
|
193
|
+
|
|
194
|
+
### `retainSearchParams` — Keep Params Across Navigation
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
import { createRootRoute, retainSearchParams } from "@tanstack/react-router";
|
|
198
|
+
import { z } from "zod";
|
|
199
|
+
|
|
200
|
+
const rootSearchSchema = z.object({
|
|
201
|
+
debug: z.boolean().optional(),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
export const Route = createRootRoute({
|
|
205
|
+
validateSearch: rootSearchSchema,
|
|
206
|
+
search: {
|
|
207
|
+
middlewares: [retainSearchParams(["debug"])],
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### `stripSearchParams` — Remove Default Values from URL
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
|
|
216
|
+
import { z } from "zod";
|
|
217
|
+
|
|
218
|
+
const defaults = { sort: "newest", page: 1 };
|
|
219
|
+
|
|
220
|
+
const searchSchema = z.object({
|
|
221
|
+
sort: z.string().default(defaults.sort),
|
|
222
|
+
page: z.number().default(defaults.page),
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
export const Route = createFileRoute("/items")({
|
|
226
|
+
validateSearch: searchSchema,
|
|
227
|
+
search: {
|
|
228
|
+
middlewares: [stripSearchParams(defaults)],
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Chaining Middlewares
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
export const Route = createFileRoute("/search")({
|
|
237
|
+
validateSearch: z.object({
|
|
238
|
+
retainMe: z.string().optional(),
|
|
239
|
+
arrayWithDefaults: z.string().array().default(["foo", "bar"]),
|
|
240
|
+
required: z.string(),
|
|
241
|
+
}),
|
|
242
|
+
search: {
|
|
243
|
+
middlewares: [
|
|
244
|
+
retainSearchParams(["retainMe"]),
|
|
245
|
+
stripSearchParams({ arrayWithDefaults: ["foo", "bar"] }),
|
|
246
|
+
],
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Custom Serialization
|
|
252
|
+
|
|
253
|
+
Override the default JSON serialization at the router level:
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { createRouter, parseSearchWith, stringifySearchWith } from "@tanstack/react-router";
|
|
257
|
+
|
|
258
|
+
const router = createRouter({
|
|
259
|
+
routeTree,
|
|
260
|
+
// Example: use JSURL2 for compact, human-readable URLs
|
|
261
|
+
parseSearch: parseSearchWith(parse),
|
|
262
|
+
stringifySearch: stringifySearchWith(stringify),
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Using Search Params in Loaders via `loaderDeps`
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
export const Route = createFileRoute("/products")({
|
|
270
|
+
validateSearch: productSearchSchema,
|
|
271
|
+
// Pick ONLY the params the loader needs — not the entire search object
|
|
272
|
+
loaderDeps: ({ search }) => ({ page: search.page }),
|
|
273
|
+
loader: async ({ deps }) => {
|
|
274
|
+
return fetchProducts({ page: deps.page });
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Common Mistakes
|
|
280
|
+
|
|
281
|
+
### 1. HIGH: Using zod v3's `.catch()` with `zodValidator()` instead of adapter `fallback()`
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
// WRONG — .catch() with zodValidator makes the type unknown
|
|
285
|
+
const schema = z.object({ page: z.number().catch(1) });
|
|
286
|
+
validateSearch: zodValidator(schema); // page is typed as unknown!
|
|
287
|
+
|
|
288
|
+
// CORRECT — fallback() preserves the inferred type
|
|
289
|
+
import { fallback } from "@tanstack/zod-adapter";
|
|
290
|
+
const schema = z.object({ page: fallback(z.number(), 1) });
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Important:** This only applies when using Zod v3, not when using Zod v4. For v4, using `.catch()` is correct.
|
|
294
|
+
|
|
295
|
+
### 2. HIGH: Returning entire search object from `loaderDeps`
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
// WRONG — loader re-runs on ANY search param change
|
|
299
|
+
loaderDeps: ({ search }) => search;
|
|
300
|
+
|
|
301
|
+
// CORRECT — loader only re-runs when page changes
|
|
302
|
+
loaderDeps: ({ search }) => ({ page: search.page });
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### 3. HIGH: Passing Date objects in search params
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
// WRONG — Date does not serialize correctly to JSON in URLs
|
|
309
|
+
<Link search={{ startDate: new Date() }}>
|
|
310
|
+
|
|
311
|
+
// CORRECT — convert to ISO string
|
|
312
|
+
<Link search={{ startDate: new Date().toISOString() }}>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### 4. MEDIUM: Parent route missing `validateSearch` blocks inheritance
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
// WRONG — child cannot access shared params
|
|
319
|
+
export const Route = createRootRoute({
|
|
320
|
+
component: RootComponent,
|
|
321
|
+
// no validateSearch!
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// CORRECT — parent must define validateSearch for children to inherit
|
|
325
|
+
export const Route = createRootRoute({
|
|
326
|
+
validateSearch: globalSearchSchema,
|
|
327
|
+
component: RootComponent,
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 5. HIGH (cross-skill): Using search as object instead of function loses params
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
// WRONG — replaces ALL search params, losing any existing ones
|
|
335
|
+
<Link to="." search={{ page: 2 }}>Page 2</Link>
|
|
336
|
+
|
|
337
|
+
// CORRECT — preserves existing params, updates only page
|
|
338
|
+
<Link to="." search={(prev) => ({ ...prev, page: 2 })}>Page 2</Link>
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## References
|
|
342
|
+
|
|
343
|
+
- [Validation Patterns Reference](https://tanstack.com/router/latest/docs/framework/react/guide/search-params) — comprehensive patterns for all validation libraries
|