@benjavicente/router-core 1.168.9
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/LICENSE +21 -0
- package/README.md +5 -0
- package/bin/intent.js +25 -0
- package/dist/cjs/Matches.cjs +17 -0
- package/dist/cjs/Matches.cjs.map +1 -0
- package/dist/cjs/Matches.d.cts +139 -0
- package/dist/cjs/RouterProvider.d.cts +27 -0
- package/dist/cjs/config.cjs +11 -0
- package/dist/cjs/config.cjs.map +1 -0
- package/dist/cjs/config.d.cts +17 -0
- package/dist/cjs/defer.cjs +41 -0
- package/dist/cjs/defer.cjs.map +1 -0
- package/dist/cjs/defer.d.cts +37 -0
- package/dist/cjs/fileRoute.d.cts +24 -0
- package/dist/cjs/global.d.cts +7 -0
- package/dist/cjs/hash-scroll.cjs +20 -0
- package/dist/cjs/hash-scroll.cjs.map +1 -0
- package/dist/cjs/hash-scroll.d.cts +7 -0
- package/dist/cjs/history.d.cts +8 -0
- package/dist/cjs/index.cjs +96 -0
- package/dist/cjs/index.d.cts +53 -0
- package/dist/cjs/invariant.cjs +8 -0
- package/dist/cjs/invariant.cjs.map +1 -0
- package/dist/cjs/invariant.d.cts +1 -0
- package/dist/cjs/isServer/client.cjs +7 -0
- package/dist/cjs/isServer/client.cjs.map +1 -0
- package/dist/cjs/isServer/client.d.cts +1 -0
- package/dist/cjs/isServer/development.cjs +7 -0
- package/dist/cjs/isServer/development.cjs.map +1 -0
- package/dist/cjs/isServer/development.d.cts +1 -0
- package/dist/cjs/isServer/server.cjs +7 -0
- package/dist/cjs/isServer/server.cjs.map +1 -0
- package/dist/cjs/isServer/server.d.cts +1 -0
- package/dist/cjs/link.cjs +6 -0
- package/dist/cjs/link.cjs.map +1 -0
- package/dist/cjs/link.d.cts +221 -0
- package/dist/cjs/load-matches.cjs +659 -0
- package/dist/cjs/load-matches.cjs.map +1 -0
- package/dist/cjs/load-matches.d.cts +18 -0
- package/dist/cjs/location.d.cts +50 -0
- package/dist/cjs/lru-cache.cjs +70 -0
- package/dist/cjs/lru-cache.cjs.map +1 -0
- package/dist/cjs/lru-cache.d.cts +6 -0
- package/dist/cjs/manifest.cjs +18 -0
- package/dist/cjs/manifest.cjs.map +1 -0
- package/dist/cjs/manifest.d.cts +35 -0
- package/dist/cjs/new-process-route-tree.cjs +754 -0
- package/dist/cjs/new-process-route-tree.cjs.map +1 -0
- package/dist/cjs/new-process-route-tree.d.cts +236 -0
- package/dist/cjs/not-found.cjs +26 -0
- package/dist/cjs/not-found.cjs.map +1 -0
- package/dist/cjs/not-found.d.cts +32 -0
- package/dist/cjs/path.cjs +252 -0
- package/dist/cjs/path.cjs.map +1 -0
- package/dist/cjs/path.d.cts +56 -0
- package/dist/cjs/qss.cjs +70 -0
- package/dist/cjs/qss.cjs.map +1 -0
- package/dist/cjs/qss.d.cts +33 -0
- package/dist/cjs/redirect.cjs +56 -0
- package/dist/cjs/redirect.cjs.map +1 -0
- package/dist/cjs/redirect.d.cts +77 -0
- package/dist/cjs/rewrite.cjs +68 -0
- package/dist/cjs/rewrite.cjs.map +1 -0
- package/dist/cjs/rewrite.d.cts +30 -0
- package/dist/cjs/root.cjs +7 -0
- package/dist/cjs/root.cjs.map +1 -0
- package/dist/cjs/root.d.cts +3 -0
- package/dist/cjs/route.cjs +100 -0
- package/dist/cjs/route.cjs.map +1 -0
- package/dist/cjs/route.d.cts +552 -0
- package/dist/cjs/routeInfo.d.cts +54 -0
- package/dist/cjs/router.cjs +1173 -0
- package/dist/cjs/router.cjs.map +1 -0
- package/dist/cjs/router.d.cts +734 -0
- package/dist/cjs/scroll-restoration-inline.cjs +6 -0
- package/dist/cjs/scroll-restoration-inline.cjs.map +1 -0
- package/dist/cjs/scroll-restoration-inline.d.cts +6 -0
- package/dist/cjs/scroll-restoration-script/client.cjs +9 -0
- package/dist/cjs/scroll-restoration-script/client.cjs.map +1 -0
- package/dist/cjs/scroll-restoration-script/client.d.cts +2 -0
- package/dist/cjs/scroll-restoration-script/server.cjs +30 -0
- package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -0
- package/dist/cjs/scroll-restoration-script/server.d.cts +2 -0
- package/dist/cjs/scroll-restoration.cjs +191 -0
- package/dist/cjs/scroll-restoration.cjs.map +1 -0
- package/dist/cjs/scroll-restoration.d.cts +38 -0
- package/dist/cjs/searchMiddleware.cjs +55 -0
- package/dist/cjs/searchMiddleware.cjs.map +1 -0
- package/dist/cjs/searchMiddleware.d.cts +25 -0
- package/dist/cjs/searchParams.cjs +65 -0
- package/dist/cjs/searchParams.cjs.map +1 -0
- package/dist/cjs/searchParams.d.cts +31 -0
- package/dist/cjs/ssr/client.cjs +7 -0
- package/dist/cjs/ssr/client.d.cts +6 -0
- package/dist/cjs/ssr/constants.cjs +8 -0
- package/dist/cjs/ssr/constants.cjs.map +1 -0
- package/dist/cjs/ssr/constants.d.cts +3 -0
- package/dist/cjs/ssr/createRequestHandler.cjs +44 -0
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -0
- package/dist/cjs/ssr/createRequestHandler.d.cts +9 -0
- package/dist/cjs/ssr/handlerCallback.cjs +8 -0
- package/dist/cjs/ssr/handlerCallback.cjs.map +1 -0
- package/dist/cjs/ssr/handlerCallback.d.cts +9 -0
- package/dist/cjs/ssr/headers.cjs +21 -0
- package/dist/cjs/ssr/headers.cjs.map +1 -0
- package/dist/cjs/ssr/headers.d.cts +3 -0
- package/dist/cjs/ssr/json.cjs +11 -0
- package/dist/cjs/ssr/json.cjs.map +1 -0
- package/dist/cjs/ssr/json.d.cts +10 -0
- package/dist/cjs/ssr/serializer/RawStream.cjs +287 -0
- package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/RawStream.d.cts +64 -0
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs +32 -0
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.d.cts +9 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs +13 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -0
- package/dist/cjs/ssr/serializer/transformer.cjs +53 -0
- package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/transformer.d.cts +91 -0
- package/dist/cjs/ssr/server.cjs +13 -0
- package/dist/cjs/ssr/server.d.cts +6 -0
- package/dist/cjs/ssr/ssr-client.cjs +183 -0
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -0
- package/dist/cjs/ssr/ssr-client.d.cts +10 -0
- package/dist/cjs/ssr/ssr-match-id.cjs +12 -0
- package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -0
- package/dist/cjs/ssr/ssr-match-id.d.cts +2 -0
- package/dist/cjs/ssr/ssr-server.cjs +279 -0
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -0
- package/dist/cjs/ssr/ssr-server.d.cts +42 -0
- package/dist/cjs/ssr/transformStreamWithRouter.cjs +327 -0
- package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -0
- package/dist/cjs/ssr/transformStreamWithRouter.d.cts +11 -0
- package/dist/cjs/ssr/tsrScript.cjs +6 -0
- package/dist/cjs/ssr/tsrScript.cjs.map +1 -0
- package/dist/cjs/ssr/tsrScript.d.cts +1 -0
- package/dist/cjs/ssr/types.d.cts +30 -0
- package/dist/cjs/stores.cjs +148 -0
- package/dist/cjs/stores.cjs.map +1 -0
- package/dist/cjs/stores.d.cts +70 -0
- package/dist/cjs/structuralSharing.d.cts +4 -0
- package/dist/cjs/typePrimitives.d.cts +65 -0
- package/dist/cjs/useLoaderData.d.cts +5 -0
- package/dist/cjs/useLoaderDeps.d.cts +5 -0
- package/dist/cjs/useNavigate.d.cts +3 -0
- package/dist/cjs/useParams.d.cts +5 -0
- package/dist/cjs/useRouteContext.d.cts +9 -0
- package/dist/cjs/useSearch.d.cts +5 -0
- package/dist/cjs/utils.cjs +339 -0
- package/dist/cjs/utils.cjs.map +1 -0
- package/dist/cjs/utils.d.cts +178 -0
- package/dist/cjs/validators.d.cts +51 -0
- package/dist/esm/Matches.d.ts +139 -0
- package/dist/esm/Matches.js +17 -0
- package/dist/esm/Matches.js.map +1 -0
- package/dist/esm/RouterProvider.d.ts +27 -0
- package/dist/esm/config.d.ts +17 -0
- package/dist/esm/config.js +11 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/defer.d.ts +37 -0
- package/dist/esm/defer.js +40 -0
- package/dist/esm/defer.js.map +1 -0
- package/dist/esm/fileRoute.d.ts +24 -0
- package/dist/esm/global.d.ts +7 -0
- package/dist/esm/hash-scroll.d.ts +7 -0
- package/dist/esm/hash-scroll.js +20 -0
- package/dist/esm/hash-scroll.js.map +1 -0
- package/dist/esm/history.d.ts +8 -0
- package/dist/esm/index.d.ts +53 -0
- package/dist/esm/index.js +24 -0
- package/dist/esm/invariant.d.ts +1 -0
- package/dist/esm/invariant.js +8 -0
- package/dist/esm/invariant.js.map +1 -0
- package/dist/esm/isServer/client.d.ts +1 -0
- package/dist/esm/isServer/client.js +6 -0
- package/dist/esm/isServer/client.js.map +1 -0
- package/dist/esm/isServer/development.d.ts +1 -0
- package/dist/esm/isServer/development.js +6 -0
- package/dist/esm/isServer/development.js.map +1 -0
- package/dist/esm/isServer/server.d.ts +1 -0
- package/dist/esm/isServer/server.js +6 -0
- package/dist/esm/isServer/server.js.map +1 -0
- package/dist/esm/link.d.ts +221 -0
- package/dist/esm/link.js +6 -0
- package/dist/esm/link.js.map +1 -0
- package/dist/esm/load-matches.d.ts +18 -0
- package/dist/esm/load-matches.js +657 -0
- package/dist/esm/load-matches.js.map +1 -0
- package/dist/esm/location.d.ts +50 -0
- package/dist/esm/lru-cache.d.ts +6 -0
- package/dist/esm/lru-cache.js +70 -0
- package/dist/esm/lru-cache.js.map +1 -0
- package/dist/esm/manifest.d.ts +35 -0
- package/dist/esm/manifest.js +17 -0
- package/dist/esm/manifest.js.map +1 -0
- package/dist/esm/new-process-route-tree.d.ts +236 -0
- package/dist/esm/new-process-route-tree.js +749 -0
- package/dist/esm/new-process-route-tree.js.map +1 -0
- package/dist/esm/not-found.d.ts +32 -0
- package/dist/esm/not-found.js +25 -0
- package/dist/esm/not-found.js.map +1 -0
- package/dist/esm/path.d.ts +56 -0
- package/dist/esm/path.js +243 -0
- package/dist/esm/path.js.map +1 -0
- package/dist/esm/qss.d.ts +33 -0
- package/dist/esm/qss.js +69 -0
- package/dist/esm/qss.js.map +1 -0
- package/dist/esm/redirect.d.ts +77 -0
- package/dist/esm/redirect.js +53 -0
- package/dist/esm/redirect.js.map +1 -0
- package/dist/esm/rewrite.d.ts +30 -0
- package/dist/esm/rewrite.js +65 -0
- package/dist/esm/rewrite.js.map +1 -0
- package/dist/esm/root.d.ts +3 -0
- package/dist/esm/root.js +7 -0
- package/dist/esm/root.js.map +1 -0
- package/dist/esm/route.d.ts +552 -0
- package/dist/esm/route.js +98 -0
- package/dist/esm/route.js.map +1 -0
- package/dist/esm/routeInfo.d.ts +54 -0
- package/dist/esm/router.d.ts +734 -0
- package/dist/esm/router.js +1165 -0
- package/dist/esm/router.js.map +1 -0
- package/dist/esm/scroll-restoration-inline.d.ts +6 -0
- package/dist/esm/scroll-restoration-inline.js +6 -0
- package/dist/esm/scroll-restoration-inline.js.map +1 -0
- package/dist/esm/scroll-restoration-script/client.d.ts +2 -0
- package/dist/esm/scroll-restoration-script/client.js +8 -0
- package/dist/esm/scroll-restoration-script/client.js.map +1 -0
- package/dist/esm/scroll-restoration-script/server.d.ts +2 -0
- package/dist/esm/scroll-restoration-script/server.js +29 -0
- package/dist/esm/scroll-restoration-script/server.js.map +1 -0
- package/dist/esm/scroll-restoration.d.ts +38 -0
- package/dist/esm/scroll-restoration.js +187 -0
- package/dist/esm/scroll-restoration.js.map +1 -0
- package/dist/esm/searchMiddleware.d.ts +25 -0
- package/dist/esm/searchMiddleware.js +54 -0
- package/dist/esm/searchMiddleware.js.map +1 -0
- package/dist/esm/searchParams.d.ts +31 -0
- package/dist/esm/searchParams.js +62 -0
- package/dist/esm/searchParams.js.map +1 -0
- package/dist/esm/ssr/client.d.ts +6 -0
- package/dist/esm/ssr/client.js +4 -0
- package/dist/esm/ssr/constants.d.ts +3 -0
- package/dist/esm/ssr/constants.js +7 -0
- package/dist/esm/ssr/constants.js.map +1 -0
- package/dist/esm/ssr/createRequestHandler.d.ts +9 -0
- package/dist/esm/ssr/createRequestHandler.js +44 -0
- package/dist/esm/ssr/createRequestHandler.js.map +1 -0
- package/dist/esm/ssr/handlerCallback.d.ts +9 -0
- package/dist/esm/ssr/handlerCallback.js +8 -0
- package/dist/esm/ssr/handlerCallback.js.map +1 -0
- package/dist/esm/ssr/headers.d.ts +3 -0
- package/dist/esm/ssr/headers.js +21 -0
- package/dist/esm/ssr/headers.js.map +1 -0
- package/dist/esm/ssr/json.d.ts +10 -0
- package/dist/esm/ssr/json.js +11 -0
- package/dist/esm/ssr/json.js.map +1 -0
- package/dist/esm/ssr/serializer/RawStream.d.ts +64 -0
- package/dist/esm/ssr/serializer/RawStream.js +282 -0
- package/dist/esm/ssr/serializer/RawStream.js.map +1 -0
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.d.ts +9 -0
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js +33 -0
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -0
- package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -0
- package/dist/esm/ssr/serializer/seroval-plugins.js +13 -0
- package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -0
- package/dist/esm/ssr/serializer/transformer.d.ts +91 -0
- package/dist/esm/ssr/serializer/transformer.js +51 -0
- package/dist/esm/ssr/serializer/transformer.js.map +1 -0
- package/dist/esm/ssr/server.d.ts +6 -0
- package/dist/esm/ssr/server.js +5 -0
- package/dist/esm/ssr/ssr-client.d.ts +10 -0
- package/dist/esm/ssr/ssr-client.js +183 -0
- package/dist/esm/ssr/ssr-client.js.map +1 -0
- package/dist/esm/ssr/ssr-match-id.d.ts +2 -0
- package/dist/esm/ssr/ssr-match-id.js +11 -0
- package/dist/esm/ssr/ssr-match-id.js.map +1 -0
- package/dist/esm/ssr/ssr-server.d.ts +42 -0
- package/dist/esm/ssr/ssr-server.js +277 -0
- package/dist/esm/ssr/ssr-server.js.map +1 -0
- package/dist/esm/ssr/transformStreamWithRouter.d.ts +11 -0
- package/dist/esm/ssr/transformStreamWithRouter.js +325 -0
- package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -0
- package/dist/esm/ssr/tsrScript.d.ts +0 -0
- package/dist/esm/ssr/tsrScript.js +6 -0
- package/dist/esm/ssr/tsrScript.js.map +1 -0
- package/dist/esm/ssr/types.d.ts +30 -0
- package/dist/esm/stores.d.ts +70 -0
- package/dist/esm/stores.js +146 -0
- package/dist/esm/stores.js.map +1 -0
- package/dist/esm/structuralSharing.d.ts +4 -0
- package/dist/esm/typePrimitives.d.ts +65 -0
- package/dist/esm/useLoaderData.d.ts +5 -0
- package/dist/esm/useLoaderDeps.d.ts +5 -0
- package/dist/esm/useNavigate.d.ts +3 -0
- package/dist/esm/useParams.d.ts +5 -0
- package/dist/esm/useRouteContext.d.ts +9 -0
- package/dist/esm/useSearch.d.ts +5 -0
- package/dist/esm/utils.d.ts +178 -0
- package/dist/esm/utils.js +322 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/esm/validators.d.ts +51 -0
- package/package.json +200 -0
- 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 +349 -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/Matches.ts +291 -0
- package/src/RouterProvider.ts +47 -0
- package/src/config.ts +42 -0
- package/src/defer.ts +69 -0
- package/src/fileRoute.ts +164 -0
- package/src/global.ts +9 -0
- package/src/hash-scroll.ts +21 -0
- package/src/history.ts +9 -0
- package/src/index.ts +471 -0
- package/src/invariant.ts +3 -0
- package/src/isServer/client.ts +1 -0
- package/src/isServer/development.ts +2 -0
- package/src/isServer/server.ts +1 -0
- package/src/link.ts +704 -0
- package/src/load-matches.ts +1281 -0
- package/src/location.ts +51 -0
- package/src/lru-cache.ts +74 -0
- package/src/manifest.ts +68 -0
- package/src/new-process-route-tree.ts +1387 -0
- package/src/not-found.ts +41 -0
- package/src/path.ts +436 -0
- package/src/qss.ts +81 -0
- package/src/redirect.ts +179 -0
- package/src/rewrite.ts +93 -0
- package/src/root.ts +3 -0
- package/src/route.ts +2235 -0
- package/src/routeInfo.ts +235 -0
- package/src/router.ts +3207 -0
- package/src/scroll-restoration-inline.ts +81 -0
- package/src/scroll-restoration-script/client.ts +5 -0
- package/src/scroll-restoration-script/server.ts +64 -0
- package/src/scroll-restoration.ts +357 -0
- package/src/searchMiddleware.ts +76 -0
- package/src/searchParams.ts +90 -0
- package/src/ssr/client.ts +6 -0
- package/src/ssr/constants.ts +3 -0
- package/src/ssr/createRequestHandler.ts +98 -0
- package/src/ssr/handlerCallback.ts +15 -0
- package/src/ssr/headers.ts +40 -0
- package/src/ssr/json.ts +16 -0
- package/src/ssr/serializer/RawStream.ts +464 -0
- package/src/ssr/serializer/ShallowErrorPlugin.ts +43 -0
- package/src/ssr/serializer/seroval-plugins.ts +12 -0
- package/src/ssr/serializer/transformer.ts +312 -0
- package/src/ssr/server.ts +14 -0
- package/src/ssr/ssr-client.ts +313 -0
- package/src/ssr/ssr-match-id.ts +7 -0
- package/src/ssr/ssr-server.ts +425 -0
- package/src/ssr/transformStreamWithRouter.ts +493 -0
- package/src/ssr/tsrScript.ts +20 -0
- package/src/ssr/types.ts +41 -0
- package/src/stores.ts +342 -0
- package/src/structuralSharing.ts +7 -0
- package/src/typePrimitives.ts +181 -0
- package/src/useLoaderData.ts +20 -0
- package/src/useLoaderDeps.ts +13 -0
- package/src/useNavigate.ts +13 -0
- package/src/useParams.ts +20 -0
- package/src/useRouteContext.ts +39 -0
- package/src/useSearch.ts +20 -0
- package/src/utils.ts +708 -0
- package/src/validators.ts +121 -0
- package/src/vite-env.d.ts +4 -0
|
@@ -0,0 +1,349 @@
|
|
|
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()` and Zod v3, use `fallback()` from `@benjavicente/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()`.
|
|
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 @benjavicente/zod-adapter
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// src/routes/products.tsx
|
|
37
|
+
import { createFileRoute } from '@benjavicente/react-router'
|
|
38
|
+
import { z } from 'zod'
|
|
39
|
+
|
|
40
|
+
const productSearchSchema = z.object({
|
|
41
|
+
page: z.number().default(1).catch(1),
|
|
42
|
+
filter: z.string().default(''),
|
|
43
|
+
sort: z.enum(['newest', 'oldest', 'price']).default('newest').catch('newest'),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
export const Route = createFileRoute('/products')({
|
|
47
|
+
validateSearch: productSearchSchema,
|
|
48
|
+
component: ProductsPage,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
function ProductsPage() {
|
|
52
|
+
// page: number, filter: string, sort: 'newest' | 'oldest' | 'price'
|
|
53
|
+
// ALL INFERRED — do not annotate
|
|
54
|
+
const { page, filter, sort } = Route.useSearch()
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div>
|
|
58
|
+
<p>
|
|
59
|
+
Page {page}, filter: {filter}, sort: {sort}
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Reading Search Params
|
|
67
|
+
|
|
68
|
+
### In Route Components: `Route.useSearch()`
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
function ProductsPage() {
|
|
72
|
+
const { page, sort } = Route.useSearch()
|
|
73
|
+
return <div>Page {page}</div>
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### In Code-Split Components: `getRouteApi()`
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { getRouteApi } from '@benjavicente/react-router'
|
|
81
|
+
|
|
82
|
+
const routeApi = getRouteApi('/products')
|
|
83
|
+
|
|
84
|
+
function ProductFilters() {
|
|
85
|
+
const { sort } = routeApi.useSearch()
|
|
86
|
+
return <select value={sort}>{/* options */}</select>
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### From Any Component: `useSearch({ from })`
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { useSearch } from '@benjavicente/react-router'
|
|
94
|
+
|
|
95
|
+
function SortIndicator() {
|
|
96
|
+
const { sort } = useSearch({ from: '/products' })
|
|
97
|
+
return <span>Sorted by: {sort}</span>
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Loose Access: `useSearch({ strict: false })`
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
function GenericPaginator() {
|
|
105
|
+
const search = useSearch({ strict: false })
|
|
106
|
+
// search.page is number | undefined (union of all routes)
|
|
107
|
+
return <span>Page: {search.page ?? 1}</span>
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Writing Search Params
|
|
112
|
+
|
|
113
|
+
### Link with Function Form (Preserves Existing Params)
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { Link } from '@benjavicente/react-router'
|
|
117
|
+
|
|
118
|
+
function Pagination() {
|
|
119
|
+
return (
|
|
120
|
+
<Link
|
|
121
|
+
from="/products"
|
|
122
|
+
search={(prev) => ({ ...prev, page: prev.page + 1 })}
|
|
123
|
+
>
|
|
124
|
+
Next Page
|
|
125
|
+
</Link>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Link with Object Form (Replaces All Params)
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<Link to="/products" search={{ page: 1, filter: '', sort: 'newest' }}>
|
|
134
|
+
Reset
|
|
135
|
+
</Link>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Programmatic: `useNavigate()`
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
import { useNavigate } from '@benjavicente/react-router'
|
|
142
|
+
|
|
143
|
+
function SortDropdown() {
|
|
144
|
+
const navigate = useNavigate({ from: '/products' })
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<select
|
|
148
|
+
onChange={(e) => {
|
|
149
|
+
navigate({
|
|
150
|
+
search: (prev) => ({ ...prev, sort: e.target.value, page: 1 }),
|
|
151
|
+
})
|
|
152
|
+
}}
|
|
153
|
+
>
|
|
154
|
+
<option value="newest">Newest</option>
|
|
155
|
+
<option value="price">Price</option>
|
|
156
|
+
</select>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Search Param Inheritance
|
|
162
|
+
|
|
163
|
+
Parent route search params are automatically merged into child routes:
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
// src/routes/shop.tsx — parent defines shared params
|
|
167
|
+
import { createFileRoute } from '@benjavicente/react-router'
|
|
168
|
+
import { z } from 'zod'
|
|
169
|
+
|
|
170
|
+
const shopSearchSchema = z.object({
|
|
171
|
+
currency: z.enum(['USD', 'EUR']).default('USD').catch('USD'),
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
export const Route = createFileRoute('/shop')({
|
|
175
|
+
validateSearch: shopSearchSchema,
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
// src/routes/shop/products.tsx — child inherits currency
|
|
181
|
+
import { createFileRoute } from '@benjavicente/react-router'
|
|
182
|
+
|
|
183
|
+
export const Route = createFileRoute('/shop/products')({
|
|
184
|
+
component: ShopProducts,
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
function ShopProducts() {
|
|
188
|
+
// currency is available here from parent — fully typed
|
|
189
|
+
const { currency } = Route.useSearch()
|
|
190
|
+
return <div>Currency: {currency}</div>
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Search Middlewares
|
|
195
|
+
|
|
196
|
+
### `retainSearchParams` — Keep Params Across Navigation
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
import { createRootRoute, retainSearchParams } from '@benjavicente/react-router'
|
|
200
|
+
import { z } from 'zod'
|
|
201
|
+
|
|
202
|
+
const rootSearchSchema = z.object({
|
|
203
|
+
debug: z.boolean().optional(),
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
export const Route = createRootRoute({
|
|
207
|
+
validateSearch: rootSearchSchema,
|
|
208
|
+
search: {
|
|
209
|
+
middlewares: [retainSearchParams(['debug'])],
|
|
210
|
+
},
|
|
211
|
+
})
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `stripSearchParams` — Remove Default Values from URL
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import { createFileRoute, stripSearchParams } from '@benjavicente/react-router'
|
|
218
|
+
import { z } from 'zod'
|
|
219
|
+
|
|
220
|
+
const defaults = { sort: 'newest', page: 1 }
|
|
221
|
+
|
|
222
|
+
const searchSchema = z.object({
|
|
223
|
+
sort: z.string().default(defaults.sort),
|
|
224
|
+
page: z.number().default(defaults.page),
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
export const Route = createFileRoute('/items')({
|
|
228
|
+
validateSearch: searchSchema,
|
|
229
|
+
search: {
|
|
230
|
+
middlewares: [stripSearchParams(defaults)],
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Chaining Middlewares
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
export const Route = createFileRoute('/search')({
|
|
239
|
+
validateSearch: z.object({
|
|
240
|
+
retainMe: z.string().optional(),
|
|
241
|
+
arrayWithDefaults: z.string().array().default(['foo', 'bar']),
|
|
242
|
+
required: z.string(),
|
|
243
|
+
}),
|
|
244
|
+
search: {
|
|
245
|
+
middlewares: [
|
|
246
|
+
retainSearchParams(['retainMe']),
|
|
247
|
+
stripSearchParams({ arrayWithDefaults: ['foo', 'bar'] }),
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
})
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Custom Serialization
|
|
254
|
+
|
|
255
|
+
Override the default JSON serialization at the router level:
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
import {
|
|
259
|
+
createRouter,
|
|
260
|
+
parseSearchWith,
|
|
261
|
+
stringifySearchWith,
|
|
262
|
+
} from '@benjavicente/react-router'
|
|
263
|
+
|
|
264
|
+
const router = createRouter({
|
|
265
|
+
routeTree,
|
|
266
|
+
// Example: use JSURL2 for compact, human-readable URLs
|
|
267
|
+
parseSearch: parseSearchWith(parse),
|
|
268
|
+
stringifySearch: stringifySearchWith(stringify),
|
|
269
|
+
})
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Using Search Params in Loaders via `loaderDeps`
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
export const Route = createFileRoute('/products')({
|
|
276
|
+
validateSearch: productSearchSchema,
|
|
277
|
+
// Pick ONLY the params the loader needs — not the entire search object
|
|
278
|
+
loaderDeps: ({ search }) => ({ page: search.page }),
|
|
279
|
+
loader: async ({ deps }) => {
|
|
280
|
+
return fetchProducts({ page: deps.page })
|
|
281
|
+
},
|
|
282
|
+
})
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Common Mistakes
|
|
286
|
+
|
|
287
|
+
### 1. HIGH: Using zod v3's `.catch()` with `zodValidator()` instead of adapter `fallback()`
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
// WRONG — .catch() with zodValidator makes the type unknown
|
|
291
|
+
const schema = z.object({ page: z.number().catch(1) })
|
|
292
|
+
validateSearch: zodValidator(schema) // page is typed as unknown!
|
|
293
|
+
|
|
294
|
+
// CORRECT — fallback() preserves the inferred type
|
|
295
|
+
import { fallback } from '@benjavicente/zod-adapter'
|
|
296
|
+
const schema = z.object({ page: fallback(z.number(), 1) })
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Important:** This only applies when using Zod v3, not when using Zod v4. For v4, using `.catch()` is correct.
|
|
300
|
+
|
|
301
|
+
### 2. HIGH: Returning entire search object from `loaderDeps`
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
// WRONG — loader re-runs on ANY search param change
|
|
305
|
+
loaderDeps: ({ search }) => search
|
|
306
|
+
|
|
307
|
+
// CORRECT — loader only re-runs when page changes
|
|
308
|
+
loaderDeps: ({ search }) => ({ page: search.page })
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### 3. HIGH: Passing Date objects in search params
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
// WRONG — Date does not serialize correctly to JSON in URLs
|
|
315
|
+
<Link search={{ startDate: new Date() }}>
|
|
316
|
+
|
|
317
|
+
// CORRECT — convert to ISO string
|
|
318
|
+
<Link search={{ startDate: new Date().toISOString() }}>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 4. MEDIUM: Parent route missing `validateSearch` blocks inheritance
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
// WRONG — child cannot access shared params
|
|
325
|
+
export const Route = createRootRoute({
|
|
326
|
+
component: RootComponent,
|
|
327
|
+
// no validateSearch!
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
// CORRECT — parent must define validateSearch for children to inherit
|
|
331
|
+
export const Route = createRootRoute({
|
|
332
|
+
validateSearch: globalSearchSchema,
|
|
333
|
+
component: RootComponent,
|
|
334
|
+
})
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### 5. HIGH (cross-skill): Using search as object instead of function loses params
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
// WRONG — replaces ALL search params, losing any existing ones
|
|
341
|
+
<Link to="." search={{ page: 2 }}>Page 2</Link>
|
|
342
|
+
|
|
343
|
+
// CORRECT — preserves existing params, updates only page
|
|
344
|
+
<Link to="." search={(prev) => ({ ...prev, page: 2 })}>Page 2</Link>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## References
|
|
348
|
+
|
|
349
|
+
- [Validation Patterns Reference](./references/validation-patterns.md) — comprehensive patterns for all validation libraries
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
# Search Param Validation Patterns Reference
|
|
2
|
+
|
|
3
|
+
Comprehensive validation patterns for TanStack Router search params across all supported validation approaches.
|
|
4
|
+
|
|
5
|
+
## Zod with `@benjavicente/zod-adapter`
|
|
6
|
+
|
|
7
|
+
Zod v3 does not implement Standard Schema, so the `@benjavicente/zod-adapter` wrapper is required. Always use `fallback()` from the adapter instead of zod's `.catch()`. Always wrap with `zodValidator()`.
|
|
8
|
+
|
|
9
|
+
### Basic Types
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { zodValidator, fallback } from '@benjavicente/zod-adapter'
|
|
13
|
+
import { z } from 'zod'
|
|
14
|
+
|
|
15
|
+
const schema = z.object({
|
|
16
|
+
count: fallback(z.number(), 0),
|
|
17
|
+
name: fallback(z.string(), ''),
|
|
18
|
+
active: fallback(z.boolean(), true),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export const Route = createFileRoute('/example')({
|
|
22
|
+
validateSearch: zodValidator(schema),
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Optional Params
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
const schema = z.object({
|
|
30
|
+
// Truly optional — can be undefined in component
|
|
31
|
+
searchTerm: z.string().optional(),
|
|
32
|
+
// Optional in URL but always has a value in component
|
|
33
|
+
page: fallback(z.number(), 1).default(1),
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Default Values
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
const schema = z.object({
|
|
41
|
+
// .default() means the param is optional during navigation
|
|
42
|
+
// but always present (with default) when reading
|
|
43
|
+
page: fallback(z.number(), 1).default(1),
|
|
44
|
+
sort: fallback(z.enum(['name', 'date', 'price']), 'name').default('name'),
|
|
45
|
+
ascending: fallback(z.boolean(), true).default(true),
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Array Params
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
const schema = z.object({
|
|
53
|
+
tags: fallback(z.string().array(), []).default([]),
|
|
54
|
+
selectedIds: fallback(z.number().array(), []).default([]),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// URL: /items?tags=%5B%22react%22%2C%22typescript%22%5D&selectedIds=%5B1%2C2%2C3%5D
|
|
58
|
+
// Parsed: { tags: ['react', 'typescript'], selectedIds: [1, 2, 3] }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Nested Object Params
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
const schema = z.object({
|
|
65
|
+
filters: fallback(
|
|
66
|
+
z.object({
|
|
67
|
+
status: z.enum(['active', 'inactive']).optional(),
|
|
68
|
+
tags: z.string().array().optional(),
|
|
69
|
+
priceRange: z
|
|
70
|
+
.object({
|
|
71
|
+
min: z.number().min(0),
|
|
72
|
+
max: z.number().min(0),
|
|
73
|
+
})
|
|
74
|
+
.optional(),
|
|
75
|
+
}),
|
|
76
|
+
{},
|
|
77
|
+
).default({}),
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Enum with Constraints
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
const schema = z.object({
|
|
85
|
+
sort: fallback(z.enum(['newest', 'oldest', 'price']), 'newest').default(
|
|
86
|
+
'newest',
|
|
87
|
+
),
|
|
88
|
+
page: fallback(z.number().int().min(1).max(1000), 1).default(1),
|
|
89
|
+
limit: fallback(z.number().int().min(10).max(100), 20).default(20),
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Discriminated Union
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
const schema = z.object({
|
|
97
|
+
searchType: fallback(z.enum(['basic', 'advanced']), 'basic').default('basic'),
|
|
98
|
+
query: fallback(z.string(), '').default(''),
|
|
99
|
+
// Advanced-only fields are optional
|
|
100
|
+
category: z.string().optional(),
|
|
101
|
+
minPrice: z.number().optional(),
|
|
102
|
+
maxPrice: z.number().optional(),
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
For true discriminated union validation:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
const basicSearch = z.object({
|
|
110
|
+
searchType: z.literal('basic'),
|
|
111
|
+
query: z.string(),
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
const advancedSearch = z.object({
|
|
115
|
+
searchType: z.literal('advanced'),
|
|
116
|
+
query: z.string(),
|
|
117
|
+
category: z.string(),
|
|
118
|
+
minPrice: z.number(),
|
|
119
|
+
maxPrice: z.number(),
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const schema = z.discriminatedUnion('searchType', [basicSearch, advancedSearch])
|
|
123
|
+
|
|
124
|
+
export const Route = createFileRoute('/search')({
|
|
125
|
+
validateSearch: zodValidator(schema),
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Input Transforms (String to Number)
|
|
130
|
+
|
|
131
|
+
When using the zod adapter with transforms, configure `input` and `output` types:
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
const schema = z.object({
|
|
135
|
+
page: fallback(z.number(), 1).default(1),
|
|
136
|
+
filter: fallback(z.string(), '').default(''),
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
export const Route = createFileRoute('/items')({
|
|
140
|
+
// Default: input type used for navigation, output type used for reading
|
|
141
|
+
validateSearch: zodValidator(schema),
|
|
142
|
+
|
|
143
|
+
// Advanced: swap input/output inference
|
|
144
|
+
// validateSearch: zodValidator({ schema, input: 'output', output: 'input' }),
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Schema Composition
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
const paginationSchema = z.object({
|
|
152
|
+
page: fallback(z.number().int().positive(), 1).default(1),
|
|
153
|
+
limit: fallback(z.number().int().min(1).max(100), 20).default(20),
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
const sortSchema = z.object({
|
|
157
|
+
sortBy: z.enum(['name', 'date', 'relevance']).optional(),
|
|
158
|
+
sortOrder: z.enum(['asc', 'desc']).optional(),
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Compose for specific routes
|
|
162
|
+
const productSearchSchema = paginationSchema.extend({
|
|
163
|
+
category: z.string().optional(),
|
|
164
|
+
inStock: fallback(z.boolean(), true).default(true),
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const userSearchSchema = paginationSchema.merge(sortSchema).extend({
|
|
168
|
+
role: z.enum(['admin', 'user']).optional(),
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Valibot (Standard Schema)
|
|
175
|
+
|
|
176
|
+
Valibot 1.0+ implements Standard Schema. No adapter wrapper needed — pass the schema directly to `validateSearch`. The `@benjavicente/valibot-adapter` is optional and only needed for explicit input/output type control.
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm install valibot
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
import { createFileRoute } from '@benjavicente/react-router'
|
|
184
|
+
import * as v from 'valibot'
|
|
185
|
+
|
|
186
|
+
const productSearchSchema = v.object({
|
|
187
|
+
page: v.optional(v.fallback(v.number(), 1), 1),
|
|
188
|
+
filter: v.optional(v.fallback(v.string(), ''), ''),
|
|
189
|
+
sort: v.optional(
|
|
190
|
+
v.fallback(v.picklist(['newest', 'oldest', 'price']), 'newest'),
|
|
191
|
+
'newest',
|
|
192
|
+
),
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
export const Route = createFileRoute('/products')({
|
|
196
|
+
// Pass schema directly — Standard Schema compliant
|
|
197
|
+
validateSearch: productSearchSchema,
|
|
198
|
+
component: ProductsPage,
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
function ProductsPage() {
|
|
202
|
+
const { page, filter, sort } = Route.useSearch()
|
|
203
|
+
return <div>Page {page}</div>
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Valibot with Constraints
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import * as v from 'valibot'
|
|
211
|
+
|
|
212
|
+
const schema = v.object({
|
|
213
|
+
page: v.optional(
|
|
214
|
+
v.fallback(v.pipe(v.number(), v.integer(), v.minValue(1)), 1),
|
|
215
|
+
1,
|
|
216
|
+
),
|
|
217
|
+
query: v.optional(v.pipe(v.string(), v.minLength(1), v.maxLength(100))),
|
|
218
|
+
tags: v.optional(v.fallback(v.array(v.string()), []), []),
|
|
219
|
+
})
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Valibot with Adapter (Alternative)
|
|
223
|
+
|
|
224
|
+
If you need explicit input/output type control:
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { valibotValidator } from '@benjavicente/valibot-adapter'
|
|
228
|
+
import * as v from 'valibot'
|
|
229
|
+
|
|
230
|
+
const schema = v.object({
|
|
231
|
+
page: v.optional(v.fallback(v.number(), 1), 1),
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
export const Route = createFileRoute('/items')({
|
|
235
|
+
validateSearch: valibotValidator(schema),
|
|
236
|
+
})
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## ArkType (Standard Schema)
|
|
242
|
+
|
|
243
|
+
ArkType 2.0-rc+ implements Standard Schema. No adapter needed — pass the type directly to `validateSearch`. The `@benjavicente/arktype-adapter` is optional and only needed for explicit input/output type control.
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
npm install arktype
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
import { createFileRoute } from '@benjavicente/react-router'
|
|
251
|
+
import { type } from 'arktype'
|
|
252
|
+
|
|
253
|
+
const productSearchSchema = type({
|
|
254
|
+
page: 'number = 1',
|
|
255
|
+
filter: 'string = ""',
|
|
256
|
+
sort: '"newest" | "oldest" | "price" = "newest"',
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
export const Route = createFileRoute('/products')({
|
|
260
|
+
// Pass directly — Standard Schema compliant
|
|
261
|
+
validateSearch: productSearchSchema,
|
|
262
|
+
component: ProductsPage,
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
function ProductsPage() {
|
|
266
|
+
const { page, filter, sort } = Route.useSearch()
|
|
267
|
+
return <div>Page {page}</div>
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### ArkType with Constraints
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
import { type } from 'arktype'
|
|
275
|
+
|
|
276
|
+
const searchSchema = type({
|
|
277
|
+
'query?': 'string>0&<=100',
|
|
278
|
+
page: 'number>0 = 1',
|
|
279
|
+
'sortBy?': "'name'|'date'|'relevance'",
|
|
280
|
+
'filters?': 'string[]',
|
|
281
|
+
})
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Manual Validation Function
|
|
287
|
+
|
|
288
|
+
For full control without any library. The function receives raw JSON-parsed (but unvalidated) search params.
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
import { createFileRoute } from '@benjavicente/react-router'
|
|
292
|
+
|
|
293
|
+
type ProductSearch = {
|
|
294
|
+
page: number
|
|
295
|
+
filter: string
|
|
296
|
+
sort: 'newest' | 'oldest' | 'price'
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export const Route = createFileRoute('/products')({
|
|
300
|
+
validateSearch: (search: Record<string, unknown>): ProductSearch => ({
|
|
301
|
+
page: Number(search?.page ?? 1),
|
|
302
|
+
filter: (search.filter as string) || '',
|
|
303
|
+
sort:
|
|
304
|
+
search.sort === 'newest' ||
|
|
305
|
+
search.sort === 'oldest' ||
|
|
306
|
+
search.sort === 'price'
|
|
307
|
+
? search.sort
|
|
308
|
+
: 'newest',
|
|
309
|
+
}),
|
|
310
|
+
component: ProductsPage,
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
function ProductsPage() {
|
|
314
|
+
const { page, filter, sort } = Route.useSearch()
|
|
315
|
+
return <div>Page {page}</div>
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Manual Validation with Error Throwing
|
|
320
|
+
|
|
321
|
+
If `validateSearch` throws, the route's `errorComponent` renders instead:
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
export const Route = createFileRoute('/products')({
|
|
325
|
+
validateSearch: (search: Record<string, unknown>) => {
|
|
326
|
+
const page = Number(search.page)
|
|
327
|
+
if (isNaN(page) || page < 1) {
|
|
328
|
+
throw new Error('Invalid page number')
|
|
329
|
+
}
|
|
330
|
+
return { page }
|
|
331
|
+
},
|
|
332
|
+
errorComponent: ({ error }) => <div>Bad search params: {error.message}</div>,
|
|
333
|
+
})
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Pattern: Object with `parse` Method
|
|
339
|
+
|
|
340
|
+
Any object with a `.parse()` method works as `validateSearch`:
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
const mySchema = {
|
|
344
|
+
parse: (input: Record<string, unknown>) => ({
|
|
345
|
+
page: Number(input.page ?? 1),
|
|
346
|
+
query: String(input.query ?? ''),
|
|
347
|
+
}),
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export const Route = createFileRoute('/search')({
|
|
351
|
+
validateSearch: mySchema,
|
|
352
|
+
})
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Dates in Search Params
|
|
358
|
+
|
|
359
|
+
Never put `Date` objects in search params. Always use ISO strings:
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
const schema = z.object({
|
|
363
|
+
// Store as string, parse in component if needed
|
|
364
|
+
startDate: z.string().optional(),
|
|
365
|
+
endDate: z.string().optional(),
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
// In component:
|
|
369
|
+
function DateFilter() {
|
|
370
|
+
const { startDate } = Route.useSearch()
|
|
371
|
+
const date = startDate ? new Date(startDate) : null
|
|
372
|
+
return <div>{date?.toLocaleDateString()}</div>
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// When navigating:
|
|
376
|
+
;<Link search={(prev) => ({ ...prev, startDate: new Date().toISOString() })}>
|
|
377
|
+
Set Start Date
|
|
378
|
+
</Link>
|
|
379
|
+
```
|