Capítulo 11 de 24

Chapter 11: Default Search Params

Core Idea

Demonstrates giving search params sane fallback values using zod's .catch() inside validateSearch, so a route like /posts/post can be linked with a partial search object (or none) and still receive fully-typed, defaulted values (postId, color) in the loader and component. This is a code-based single-file example (src/main.tsx).

Setup

  • Routing style: code-based (createRootRoute / createRoute / .addChildren)
  • Key dependencies: @tanstack/react-router, @tanstack/react-query, zod, redaxios
  • Structure: one file, src/main.tsx: fetch helpers, root/index/posts-layout/posts-index/post routes, validateSearch with zod defaults, loaderDeps tying the loader to the validated search, and the component reading useSearch().

Code Example

const postRoute = createRoute({
  getParentRoute: () => postsLayoutRoute,
  path: 'post',
  validateSearch: (
    input: { postId: number; color?: 'white' | 'red' | 'green' } & SearchSchemaInput,
  ) =>
    z
      .object({
        postId: z.number().catch(1),
        color: z.enum(['white', 'red', 'green']).catch('white'),
      })
      .parse(input),
  loaderDeps: ({ search: { postId } }) => ({ postId }),
  errorComponent: PostErrorComponent,
  loader: ({ deps: { postId } }) => fetchPost(postId),
  component: PostComponent,
})

function PostComponent() {
  const post = postRoute.useLoaderData()
  const { color } = postRoute.useSearch()
  return (
    <div className="space-y-2">
      <h4 className="text-xl font-bold">{post.title}</h4>
      <div className={`text-sm text-${color}-300`}>{post.body}</div>
    </div>
  )
}
  • What it demonstrates: z.number().catch(1) and z.enum([...]).catch('white') supply default values whenever the incoming search param is missing or fails validation, instead of throwing or requiring the caller to always pass every key.

Key Takeaways

  1. Prefer .catch(defaultValue) over .optional() when a search param should always resolve to a concrete typed value in the loader/component, it removes undefined branches from downstream code.
  2. loaderDeps should mirror only the search fields the loader actually needs (postId here, not color), so navigating with a different color doesn't needlessly re-trigger the loader.
  3. Navigating with a partial search object (e.g. search={{ postId: post.id, color: index % 2 ? 'red' : undefined }}) is safe because the validator fills in the rest with defaults.
  4. SearchSchemaInput in the validator's input type documents that the raw input may be a broader/optional shape than the parsed output.

Connects To

  • ch012-search-validator-adapters: Both chapters center on validateSearch; this one focuses on defaulting/fallback behavior with plain zod, ch012 focuses on using different validation libraries (zod, valibot, arktype) via TanStack's adapter packages for the same job.