Suchparameter (Search Params)

Ähnlich wie TanStack Query die Handhabung von Server-State in Ihren React- und Solid-Anwendungen vereinfacht hat, zielt TanStack Router darauf ab, die Leistung von URL-Suchparametern in Ihren Anwendungen zu erschließen.

🧠 Wenn Sie einen sehr alten Browser wie IE11 verwenden, benötigen Sie möglicherweise ein Polyfill für URLSearchParams.

Warum nicht einfach URLSearchParams verwenden?

Wir verstehen es, Sie hören in letzter Zeit viel "nutzen Sie die Plattform" und größtenteils stimmen wir zu. Wir glauben jedoch auch, dass es wichtig ist, zu erkennen, wo die Plattform für fortgeschrittenere Anwendungsfälle zu kurz greift, und wir glauben, dass URLSearchParams eine dieser Umstände ist.

Traditionelle Suchparameter-APIs gehen normalerweise von einigen Annahmen aus

  • Suchparameter sind immer Zeichenketten
  • Sie sind *meistens* flach
  • Serialisierung und Deserialisierung mit URLSearchParams ist gut genug (Spoiler-Alarm: ist es nicht.)
  • Änderungen an Suchparametern sind eng mit dem Pfad der URL verknüpft und müssen zusammen aktualisiert werden, auch wenn sich der Pfad nicht ändert.

Die Realität unterscheidet sich jedoch stark von diesen Annahmen.

  • Suchparameter stellen den Anwendungszustand dar, daher erwarten wir zwangsläufig, dass sie die gleiche DX wie andere Zustandsmanager aufweisen. Dies bedeutet die Möglichkeit, zwischen primitiven Wertetypen zu unterscheiden und komplexe Datenstrukturen wie verschachtelte Arrays und Objekte effizient zu speichern und zu manipulieren.
  • Es gibt viele Möglichkeiten, Zustände mit unterschiedlichen Kompromissen zu serialisieren und zu deserialisieren. Sie sollten in der Lage sein, die beste für Ihre Anwendung auszuwählen oder zumindest einen besseren Standard als URLSearchParams zu erhalten.
  • Unveränderlichkeit & strukturelles Teilen. Jedes Mal, wenn Sie URL-Suchparameter serialisieren und parsen, gehen Referenzintegrität und Objektidentität verloren, da jeder neue Parse eine brandneue Datenstruktur mit einer eindeutigen Speicherreferenz erstellt. Wenn dieser ständige Serialisierungs- und Parsingprozess nicht ordnungsgemäß verwaltet wird, kann dies zu unerwarteten und unerwünschten Leistungsproblemen führen, insbesondere in Frameworks wie React, die die Reaktivität über Unveränderlichkeit verfolgen, oder in Solid, das sich normalerweise auf die Abgleichung verlässt, um Änderungen von deserialisierten Datenquellen zu erkennen.
  • Suchparameter sind zwar ein wichtiger Teil der URL, ändern sich aber häufig unabhängig vom Pfad der URL. Ein Benutzer möchte zum Beispiel die Seitennummer einer paginierten Liste ändern, ohne den Pfad der URL zu berühren.

Suchparameter, der "OG" Zustandsmanager

Sie haben wahrscheinlich Suchparameter wie ?page=3 oder ?filter-name=tanner in der URL gesehen. Es besteht kein Zweifel, dass dies wirklich eine Form des globalen Zustands ist, der sich in der URL befindet. Es ist wertvoll, bestimmte Zustände in der URL zu speichern, weil

  • Benutzer sollten in der Lage sein,
    • Cmd/Ctrl + Klick, um einen Link in einem neuen Tab zu öffnen und den erwarteten Zustand zuverlässig zu sehen
    • Lesezeichen für Links von Ihrer Anwendung setzen und sie mit anderen teilen, in der Zusicherung, dass sie genau den Zustand sehen, wie er beim Kopieren des Links war.
    • Ihre App aktualisieren oder zwischen Seiten hin- und her navigieren, ohne ihren Zustand zu verlieren
  • Entwickler sollten einfach in der Lage sein,
    • Zustand in der URL mit der gleichen großartigen DX wie bei anderen Zustandsmanagern hinzufügen, entfernen oder ändern
    • Einfach Suchparameter aus der URL in einem Format und Typ validieren, der für ihre Anwendung sicher zu verwenden ist
    • Auf Suchparameter zugreifen und diese schreiben, ohne sich Gedanken über das zugrunde liegende Serialisierungsformat machen zu müssen

JSON-first Suchparameter

Um dies zu erreichen, ist der erste Schritt in TanStack Router ein leistungsstarker Suchparameter-Parser, der die Suchzeichenkette Ihrer URL automatisch in strukturiertes JSON umwandelt. Das bedeutet, dass Sie jede JSON-serialisierbare Datenstruktur in Ihren Suchparametern speichern können und sie wird als JSON geparst und serialisiert. Dies ist eine enorme Verbesserung gegenüber URLSearchParams, das nur begrenzte Unterstützung für arrayähnliche Strukturen und verschachtelte Daten bietet.

Zum Beispiel die Navigation zur folgenden Route

tsx
const link = (
  <Link
    to="/shop"
    search={{
      pageIndex: 3,
      includeCategories: ['electronics', 'gifts'],
      sortBy: 'price',
      desc: true,
    }}
  />
)
const link = (
  <Link
    to="/shop"
    search={{
      pageIndex: 3,
      includeCategories: ['electronics', 'gifts'],
      sortBy: 'price',
      desc: true,
    }}
  />
)

führt zur folgenden URL

/shop?pageIndex=3&includeCategories=%5B%22electronics%22%2C%22gifts%22%5D&sortBy=price&desc=true
/shop?pageIndex=3&includeCategories=%5B%22electronics%22%2C%22gifts%22%5D&sortBy=price&desc=true

Wenn diese URL geparst wird, werden die Suchparameter korrekt in das folgende JSON konvertiert

json
{
  "pageIndex": 3,
  "includeCategories": ["electronics", "gifts"],
  "sortBy": "price",
  "desc": true
}
{
  "pageIndex": 3,
  "includeCategories": ["electronics", "gifts"],
  "sortBy": "price",
  "desc": true
}

Wenn Sie bemerkt haben, gibt es hier ein paar Dinge, die passieren

  • Die erste Ebene der Suchparameter ist flach und zeichenkettenbasiert, genau wie URLSearchParams.
  • Werte der ersten Ebene, die keine Zeichenketten sind, werden korrekt als tatsächliche Zahlen und Booleans beibehalten.
  • Verschachtelte Datenstrukturen werden automatisch in URL-sichere JSON-Zeichenketten umgewandelt

🧠 Es ist üblich, dass andere Tools davon ausgehen, dass Suchparameter immer flach und zeichenkettenbasiert sind. Deshalb haben wir uns entschieden, die Dinge auf der ersten Ebene URLSearchParam-konform zu halten. Dies bedeutet letztendlich, dass TanStack Router Ihre verschachtelten Suchparameter zwar als JSON verwaltet, andere Tools jedoch weiterhin normal in die URL schreiben und Parameter der ersten Ebene lesen können.

Validieren und Typisieren von Suchparametern

Obwohl TanStack Router Suchparameter in zuverlässiges JSON parsen kann, stammen sie letztendlich von einem für den Benutzer sichtbaren Rohtext-Input. Ähnlich wie bei anderen Serialisierungsgrenzen bedeutet dies, dass sie, bevor Sie Suchparameter verwenden, in ein Format validiert werden sollten, dem Ihre Anwendung vertrauen und auf das sie sich verlassen kann.

Validierung + TypeScript!

TanStack Router bietet praktische APIs zum Validieren und Typisieren von Suchparametern. Dies beginnt alles mit der validateSearch Option der Route

tsx
// /routes/shop.products.tsx

type ProductSearchSortOptions = 'newest' | 'oldest' | 'price'

type ProductSearch = {
  page: number
  filter: string
  sort: ProductSearchSortOptions
}

export const Route = createFileRoute('/shop/products')({
  validateSearch: (search: Record<string, unknown>): ProductSearch => {
    // validate and parse the search params into a typed state
    return {
      page: Number(search?.page ?? 1),
      filter: (search.filter as string) || '',
      sort: (search.sort as ProductSearchSortOptions) || 'newest',
    }
  },
})
// /routes/shop.products.tsx

type ProductSearchSortOptions = 'newest' | 'oldest' | 'price'

type ProductSearch = {
  page: number
  filter: string
  sort: ProductSearchSortOptions
}

export const Route = createFileRoute('/shop/products')({
  validateSearch: (search: Record<string, unknown>): ProductSearch => {
    // validate and parse the search params into a typed state
    return {
      page: Number(search?.page ?? 1),
      filter: (search.filter as string) || '',
      sort: (search.sort as ProductSearchSortOptions) || 'newest',
    }
  },
})

Im obigen Beispiel validieren wir die Suchparameter der Route und geben ein typisiertes ProductSearch-Objekt zurück. Dieses typisierte Objekt steht dann anderen Optionen dieser Route und auch allen untergeordneten Routen zur Verfügung!

Validierung von Suchparametern

Die Option validateSearch ist eine Funktion, die die JSON-geparsten (aber nicht validierten) Suchparameter als Record<string, unknown> erhält und ein typisiertes Objekt Ihrer Wahl zurückgibt. Es ist am besten, sinnvolle Fallbacks für fehlerhafte oder unerwartete Suchparameter bereitzustellen, damit die Benutzererfahrung nicht unterbrochen wird.

Hier ist ein Beispiel

tsx
// /routes/shop.products.tsx

type ProductSearchSortOptions = 'newest' | 'oldest' | 'price'

type ProductSearch = {
  page: number
  filter: string
  sort: ProductSearchSortOptions
}

export const Route = createFileRoute('/shop/products')({
  validateSearch: (search: Record<string, unknown>): ProductSearch => {
    // validate and parse the search params into a typed state
    return {
      page: Number(search?.page ?? 1),
      filter: (search.filter as string) || '',
      sort: (search.sort as ProductSearchSortOptions) || 'newest',
    }
  },
})
// /routes/shop.products.tsx

type ProductSearchSortOptions = 'newest' | 'oldest' | 'price'

type ProductSearch = {
  page: number
  filter: string
  sort: ProductSearchSortOptions
}

export const Route = createFileRoute('/shop/products')({
  validateSearch: (search: Record<string, unknown>): ProductSearch => {
    // validate and parse the search params into a typed state
    return {
      page: Number(search?.page ?? 1),
      filter: (search.filter as string) || '',
      sort: (search.sort as ProductSearchSortOptions) || 'newest',
    }
  },
})

Hier ist ein Beispiel, das die Bibliothek Zod verwendet (aber Sie können jede beliebige Validierungsbibliothek verwenden), um die Suchparameter in einem Schritt sowohl zu validieren als auch zu typisieren

tsx
// /routes/shop.products.tsx

import { z } from 'zod'

const productSearchSchema = z.object({
  page: z.number().catch(1),
  filter: z.string().catch(''),
  sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
})

type ProductSearch = z.infer<typeof productSearchSchema>

export const Route = createFileRoute('/shop/products')({
  validateSearch: (search) => productSearchSchema.parse(search),
})
// /routes/shop.products.tsx

import { z } from 'zod'

const productSearchSchema = z.object({
  page: z.number().catch(1),
  filter: z.string().catch(''),
  sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
})

type ProductSearch = z.infer<typeof productSearchSchema>

export const Route = createFileRoute('/shop/products')({
  validateSearch: (search) => productSearchSchema.parse(search),
})

Da validateSearch auch ein Objekt mit der Eigenschaft parse akzeptiert, kann dies verkürzt werden zu

tsx
validateSearch: productSearchSchema
validateSearch: productSearchSchema

Im obigen Beispiel haben wir den Modifikator .catch() von Zod anstelle von .default() verwendet, um dem Benutzer keinen Fehler anzuzeigen, da wir fest davon überzeugt sind, dass bei fehlerhaften Suchparametern Sie die Benutzererfahrung in der App nicht durch eine große Fehlermeldung unterbrechen möchten. Dennoch kann es Zeiten geben, in denen Sie eine Fehlermeldung anzeigen möchten. In diesem Fall können Sie .default() anstelle von .catch() verwenden.

Die zugrunde liegende Mechanik, warum dies funktioniert, beruht darauf, dass die Funktion validateSearch einen Fehler auslöst. Wenn ein Fehler ausgelöst wird, wird die Option onError der Route ausgelöst (und error.routerCode wird auf VALIDATE_SEARCH gesetzt) und die errorComponent wird anstelle der component der Route gerendert, wo Sie den Suchparameterfehler nach Belieben behandeln können.

Adapter

Bei der Verwendung einer Bibliothek wie Zod zur Validierung von Suchparametern möchten Sie möglicherweise Suchparameter transformieren, bevor Sie die Suchparameter in die URL übernehmen. Eine übliche zod transform ist z. B. default.

tsx
import { createFileRoute } from '@tanstack/solid-router'
import { z } from 'zod'

const productSearchSchema = z.object({
  page: z.number().default(1),
  filter: z.string().default(''),
  sort: z.enum(['newest', 'oldest', 'price']).default('newest'),
})

export const Route = createFileRoute('/shop/products/')({
  validateSearch: productSearchSchema,
})
import { createFileRoute } from '@tanstack/solid-router'
import { z } from 'zod'

const productSearchSchema = z.object({
  page: z.number().default(1),
  filter: z.string().default(''),
  sort: z.enum(['newest', 'oldest', 'price']).default('newest'),
})

export const Route = createFileRoute('/shop/products/')({
  validateSearch: productSearchSchema,
})

Es mag überraschen, dass bei der Navigation zu dieser Route search erforderlich ist. Der folgende Link wird mit einem Typfehler als fehlend search markiert.

tsx
<Link to="/shop/products" />
<Link to="/shop/products" />

Für Validierungsbibliotheken empfehlen wir die Verwendung von Adaptern, die die korrekten input- und output-Typen ableiten.

Zod

Ein Adapter wird für Zod bereitgestellt, der die korrekten input- und output-Typen durchleitet

tsx
import { createFileRoute } from '@tanstack/solid-router'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'

const productSearchSchema = z.object({
  page: z.number().default(1),
  filter: z.string().default(''),
  sort: z.enum(['newest', 'oldest', 'price']).default('newest'),
})

export const Route = createFileRoute('/shop/products/')({
  validateSearch: zodValidator(productSearchSchema),
})
import { createFileRoute } from '@tanstack/solid-router'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'

const productSearchSchema = z.object({
  page: z.number().default(1),
  filter: z.string().default(''),
  sort: z.enum(['newest', 'oldest', 'price']).default('newest'),
})

export const Route = createFileRoute('/shop/products/')({
  validateSearch: zodValidator(productSearchSchema),
})

Der wichtige Teil hier ist die folgende Verwendung von Link, die keine search-Parameter mehr benötigt

tsx
<Link to="/shop/products" />
<Link to="/shop/products" />

Die Verwendung von catch hier überschreibt jedoch die Typen und macht page, filter und sort zu unknown, was zu Typverlust führt. Wir haben diesen Fall behandelt, indem wir eine generische Funktion fallback bereitgestellt haben, die die Typen beibehält, aber einen fallback-Wert liefert, wenn die Validierung fehlschlägt

tsx
import { createFileRoute } from '@tanstack/solid-router'
import { fallback, zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'

const productSearchSchema = z.object({
  page: fallback(z.number(), 1).default(1),
  filter: fallback(z.string(), '').default(''),
  sort: fallback(z.enum(['newest', 'oldest', 'price']), 'newest').default(
    'newest',
  ),
})

export const Route = createFileRoute('/shop/products/')({
  validateSearch: zodValidator(productSearchSchema),
})
import { createFileRoute } from '@tanstack/solid-router'
import { fallback, zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'

const productSearchSchema = z.object({
  page: fallback(z.number(), 1).default(1),
  filter: fallback(z.string(), '').default(''),
  sort: fallback(z.enum(['newest', 'oldest', 'price']), 'newest').default(
    'newest',
  ),
})

export const Route = createFileRoute('/shop/products/')({
  validateSearch: zodValidator(productSearchSchema),
})

Daher ist bei der Navigation zu dieser Route search optional und behält die korrekten Typen bei.

Obwohl nicht empfohlen, ist es auch möglich, input und output-Typen zu konfigurieren, falls der output-Typ genauer ist als der input-Typ

tsx
const productSearchSchema = z.object({
  page: fallback(z.number(), 1).default(1),
  filter: fallback(z.string(), '').default(''),
  sort: fallback(z.enum(['newest', 'oldest', 'price']), 'newest').default(
    'newest',
  ),
})

export const Route = createFileRoute('/shop/products/')({
  validateSearch: zodValidator({
    schema: productSearchSchema,
    input: 'output',
    output: 'input',
  }),
})
const productSearchSchema = z.object({
  page: fallback(z.number(), 1).default(1),
  filter: fallback(z.string(), '').default(''),
  sort: fallback(z.enum(['newest', 'oldest', 'price']), 'newest').default(
    'newest',
  ),
})

export const Route = createFileRoute('/shop/products/')({
  validateSearch: zodValidator({
    schema: productSearchSchema,
    input: 'output',
    output: 'input',
  }),
})

Dies bietet Flexibilität bei der Wahl des Typs, den Sie für die Navigation und die Typen, die Sie für das Lesen von Suchparametern inferieren möchten.

Valibot

Warnung

Router erwartet, dass das Valibot 1.0-Paket installiert ist.

Bei der Verwendung von Valibot ist kein Adapter erforderlich, um sicherzustellen, dass die korrekten input- und output-Typen für die Navigation und das Lesen von Suchparametern verwendet werden. Dies liegt daran, dass Valibot Standard Schema implementiert.

tsx
import { createFileRoute } from '@tanstack/solid-router'
import * as v from 'valibot'

const productSearchSchema = v.object({
  page: v.optional(v.fallback(v.number(), 1), 1),
  filter: v.optional(v.fallback(v.string(), ''), ''),
  sort: v.optional(
    v.fallback(v.picklist(['newest', 'oldest', 'price']), 'newest'),
    'newest',
  ),
})

export const Route = createFileRoute('/shop/products/')({
  validateSearch: productSearchSchema,
})
import { createFileRoute } from '@tanstack/solid-router'
import * as v from 'valibot'

const productSearchSchema = v.object({
  page: v.optional(v.fallback(v.number(), 1), 1),
  filter: v.optional(v.fallback(v.string(), ''), ''),
  sort: v.optional(
    v.fallback(v.picklist(['newest', 'oldest', 'price']), 'newest'),
    'newest',
  ),
})

export const Route = createFileRoute('/shop/products/')({
  validateSearch: productSearchSchema,
})

Arktype

Warnung

Router erwartet, dass das Arktype 2.0-rc-Paket installiert ist.

Bei der Verwendung von ArkType ist kein Adapter erforderlich, um sicherzustellen, dass die korrekten input- und output-Typen für die Navigation und das Lesen von Suchparametern verwendet werden. Dies liegt daran, dass ArkType Standard Schema implementiert.

tsx
import { createFileRoute } from '@tanstack/solid-router'
import { type } from 'arktype'

const productSearchSchema = type({
  page: 'number = 1',
  filter: 'string = ""',
  sort: '"newest" | "oldest" | "price" = "newest"',
})

export const Route = createFileRoute('/shop/products/')({
  validateSearch: productSearchSchema,
})
import { createFileRoute } from '@tanstack/solid-router'
import { type } from 'arktype'

const productSearchSchema = type({
  page: 'number = 1',
  filter: 'string = ""',
  sort: '"newest" | "oldest" | "price" = "newest"',
})

export const Route = createFileRoute('/shop/products/')({
  validateSearch: productSearchSchema,
})

Effekt/Schema

Bei der Verwendung von Effect/Schema ist kein Adapter erforderlich, um sicherzustellen, dass die korrekten input- und output-Typen für die Navigation und das Lesen von Suchparametern verwendet werden. Dies liegt daran, dass Effect/Schema Standard Schema implementiert.

tsx
import { createFileRoute } from '@tanstack/solid-router'
import { Schema as S } from 'effect'

const productSearchSchema = S.standardSchemaV1(
  S.Struct({
    page: S.NumberFromString.pipe(
      S.optional,
      S.withDefaults({
        constructor: () => 1,
        decoding: () => 1,
      }),
    ),
    filter: S.String.pipe(
      S.optional,
      S.withDefaults({
        constructor: () => '',
        decoding: () => '',
      }),
    ),
    sort: S.Literal('newest', 'oldest', 'price').pipe(
      S.optional,
      S.withDefaults({
        constructor: () => 'newest' as const,
        decoding: () => 'newest' as const,
      }),
    ),
  }),
)

export const Route = createFileRoute('/shop/products/')({
  validateSearch: productSearchSchema,
})
import { createFileRoute } from '@tanstack/solid-router'
import { Schema as S } from 'effect'

const productSearchSchema = S.standardSchemaV1(
  S.Struct({
    page: S.NumberFromString.pipe(
      S.optional,
      S.withDefaults({
        constructor: () => 1,
        decoding: () => 1,
      }),
    ),
    filter: S.String.pipe(
      S.optional,
      S.withDefaults({
        constructor: () => '',
        decoding: () => '',
      }),
    ),
    sort: S.Literal('newest', 'oldest', 'price').pipe(
      S.optional,
      S.withDefaults({
        constructor: () => 'newest' as const,
        decoding: () => 'newest' as const,
      }),
    ),
  }),
)

export const Route = createFileRoute('/shop/products/')({
  validateSearch: productSearchSchema,
})

Lesen von Suchparametern

Sobald Ihre Suchparameter validiert und typisiert wurden, sind Sie endlich bereit, mit dem Lesen und Schreiben zu beginnen. Es gibt mehrere Möglichkeiten, dies in TanStack Router zu tun. Schauen wir uns diese an.

Verwendung von Suchparametern in Loadern

Bitte lesen Sie den Abschnitt Suchparameter in Loadern, um weitere Informationen darüber zu erhalten, wie Sie Suchparameter in Loadern mit der Option loaderDeps lesen.

Suchparameter werden von übergeordneten Routen geerbt

Die Suchparameter und Typen von Eltern werden beim Abstieg im Routenbaum zusammengeführt, sodass untergeordnete Routen auch Zugriff auf die Suchparameter ihrer Eltern haben.

  • shop.products.tsx
tsx
const productSearchSchema = z.object({
  page: z.number().catch(1),
  filter: z.string().catch(''),
  sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
})

type ProductSearch = z.infer<typeof productSearchSchema>

export const Route = createFileRoute('/shop/products')({
  validateSearch: productSearchSchema,
})
const productSearchSchema = z.object({
  page: z.number().catch(1),
  filter: z.string().catch(''),
  sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
})

type ProductSearch = z.infer<typeof productSearchSchema>

export const Route = createFileRoute('/shop/products')({
  validateSearch: productSearchSchema,
})
  • shop.products.$productId.tsx
tsx
export const Route = createFileRoute('/shop/products/$productId')({
  beforeLoad: ({ search }) => {
    search
    // ^? ProductSearch ✅
  },
})
export const Route = createFileRoute('/shop/products/$productId')({
  beforeLoad: ({ search }) => {
    search
    // ^? ProductSearch ✅
  },
})

Suchparameter in Komponenten

Sie können auf die validierten Suchparameter Ihrer Route in der component Ihrer Route über den Hook useSearch zugreifen.

tsx
// /routes/shop.products.tsx

export const Route = createFileRoute('/shop/products')({
  validateSearch: productSearchSchema,
})

const ProductList = () => {
  const { page, filter, sort } = Route.useSearch()

  return <div>...</div>
}
// /routes/shop.products.tsx

export const Route = createFileRoute('/shop/products')({
  validateSearch: productSearchSchema,
})

const ProductList = () => {
  const { page, filter, sort } = Route.useSearch()

  return <div>...</div>
}

Tipp

Wenn Ihre Komponente Code-Split ist, können Sie die Funktion getRouteApi verwenden, um den Import der Route-Konfiguration zu vermeiden und Zugriff auf den typisierten Hook useSearch() zu erhalten.

Suchparameter außerhalb von Routenkomponenten

Sie können auf die validierten Suchparameter Ihrer Route an beliebiger Stelle in Ihrer App über den Hook useSearch zugreifen. Durch Übergabe der from-ID/Pfads Ihrer Ursprungsroute erhalten Sie eine noch bessere Typsicherheit.

tsx
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products')({
  validateSearch: productSearchSchema,
  // ...
})

// Somewhere else...

// /components/product-list-sidebar.tsx
const routeApi = getRouteApi('/shop/products')

const ProductList = () => {
  const routeSearch = routeApi.useSearch()

  // OR

  const { page, filter, sort } = useSearch({
    from: Route.fullPath,
  })

  return <div>...</div>
}
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products')({
  validateSearch: productSearchSchema,
  // ...
})

// Somewhere else...

// /components/product-list-sidebar.tsx
const routeApi = getRouteApi('/shop/products')

const ProductList = () => {
  const routeSearch = routeApi.useSearch()

  // OR

  const { page, filter, sort } = useSearch({
    from: Route.fullPath,
  })

  return <div>...</div>
}

Oder Sie können die Typsicherheit lockern und ein optionales search-Objekt erhalten, indem Sie strict: false übergeben.

tsx
function ProductList() {
  const search = useSearch({
    strict: false,
  })
  // {
  //   page: number | undefined
  //   filter: string | undefined
  //   sort: 'newest' | 'oldest' | 'price' | undefined
  // }

  return <div>...</div>
}
function ProductList() {
  const search = useSearch({
    strict: false,
  })
  // {
  //   page: number | undefined
  //   filter: string | undefined
  //   sort: 'newest' | 'oldest' | 'price' | undefined
  // }

  return <div>...</div>
}

Schreiben von Suchparametern

Nachdem Sie nun gelernt haben, wie Sie die Suchparameter Ihrer Route lesen, werden Sie sich freuen zu wissen, dass Sie bereits die primären APIs zum Ändern und Aktualisieren gesehen haben. Lassen Sie uns uns kurz erinnern.

Der beste Weg, Suchparameter zu aktualisieren, ist die Verwendung der Prop search der Komponente <Link />.

Wenn der Suchparameter für die aktuelle Seite aktualisiert werden soll und die Prop from angegeben ist, kann die Prop to weggelassen werden.
Hier ist ein Beispiel

tsx
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products')({
  validateSearch: productSearchSchema,
})

const ProductList = () => {
  return (
    <div>
      <Link from={Route.fullPath} search={(prev) => ({ page: prev.page + 1 })}>
        Next Page
      </Link>
    </div>
  )
}
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products')({
  validateSearch: productSearchSchema,
})

const ProductList = () => {
  return (
    <div>
      <Link from={Route.fullPath} search={(prev) => ({ page: prev.page + 1 })}>
        Next Page
      </Link>
    </div>
  )
}

Wenn Sie die Suchparameter in einer generischen Komponente aktualisieren möchten, die auf mehreren Routen gerendert wird, kann die Angabe von from schwierig sein.

In diesem Szenario können Sie to="." setzen, was Ihnen Zugriff auf lose typisierte Suchparameter gewährt.
Hier ist ein Beispiel, das dies veranschaulicht

tsx
// `page` is a search param that is defined in the __root route and hence available on all routes.
const PageSelector = () => {
  return (
    <div>
      <Link to="." search={(prev) => ({ ...prev, page: prev.page + 1 })}>
        Next Page
      </Link>
    </div>
  )
}
// `page` is a search param that is defined in the __root route and hence available on all routes.
const PageSelector = () => {
  return (
    <div>
      <Link to="." search={(prev) => ({ ...prev, page: prev.page + 1 })}>
        Next Page
      </Link>
    </div>
  )
}

Wenn die generische Komponente nur in einem bestimmten Unterbaum des Routenbaums gerendert wird, können Sie diesen Unterbaum mit from angeben. Hier können Sie to='.' weglassen, wenn Sie möchten.

tsx
// `page` is a search param that is defined in the /posts route and hence available on all of its child routes.
const PageSelector = () => {
  return (
    <div>
      <Link
        from="/posts"
        to="."
        search={(prev) => ({ ...prev, page: prev.page + 1 })}
      >
        Next Page
      </Link>
    </div>
  )
// `page` is a search param that is defined in the /posts route and hence available on all of its child routes.
const PageSelector = () => {
  return (
    <div>
      <Link
        from="/posts"
        to="."
        search={(prev) => ({ ...prev, page: prev.page + 1 })}
      >
        Next Page
      </Link>
    </div>
  )

useNavigate(), navigate({ search })

Die Funktion navigate akzeptiert auch eine Option search, die genauso funktioniert wie die Prop search auf <Link />.

tsx
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products/$productId')({
  validateSearch: productSearchSchema,
})

const ProductList = () => {
  const navigate = useNavigate({ from: Route.fullPath })

  return (
    <div>
      <button
        onClick={() => {
          navigate({
            search: (prev) => ({ page: prev.page + 1 }),
          })
        }}
      >
        Next Page
      </button>
    </div>
  )
}
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products/$productId')({
  validateSearch: productSearchSchema,
})

const ProductList = () => {
  const navigate = useNavigate({ from: Route.fullPath })

  return (
    <div>
      <button
        onClick={() => {
          navigate({
            search: (prev) => ({ page: prev.page + 1 }),
          })
        }}
      >
        Next Page
      </button>
    </div>
  )
}

router.navigate({ search })

Die Funktion router.navigate funktioniert genauso wie der obige Hook/Funktion useNavigate/navigate.

Die Komponente <Navigate search /> funktioniert genauso wie der obige Hook/Funktion useNavigate/navigate, akzeptiert aber seine Optionen als Props anstelle eines Funktionsarguments.

Transformation von Suchparametern mit Such-Middlewares

Wenn Link-hrefs erstellt werden, ist standardmäßig nur die search-Eigenschaft eines <Link> für den Abfragezeichenfolgen-Teil relevant.

TanStack Router bietet eine Möglichkeit, Suchparameter zu manipulieren, bevor der href generiert wird, über Such-Middlewares. Such-Middlewares sind Funktionen, die die Suchparameter transformieren, wenn neue Links für eine Route oder ihre Nachfolger generiert werden. Sie werden auch bei der Navigation nach der Suchvalidierung ausgeführt, um die Manipulation der Abfragezeichenfolge zu ermöglichen.

Das folgende Beispiel zeigt, wie sichergestellt wird, dass für jeden Link, der erstellt wird, der Suchparameter rootValue hinzugefügt wird, falls er Teil der aktuellen Suchparameter ist. Wenn ein Link rootValue innerhalb von search angibt, wird dieser Wert für die Erstellung des Links verwendet.

tsx
import { z } from 'zod'
import { createFileRoute } from '@tanstack/solid-router'
import { zodValidator } from '@tanstack/zod-adapter'

const searchSchema = z.object({
  rootValue: z.string().optional(),
})

export const Route = createRootRoute({
  validateSearch: zodValidator(searchSchema),
  search: {
    middlewares: [
      ({ search, next }) => {
        const result = next(search)
        return {
          rootValue: search.rootValue,
          ...result,
        }
      },
    ],
  },
})
import { z } from 'zod'
import { createFileRoute } from '@tanstack/solid-router'
import { zodValidator } from '@tanstack/zod-adapter'

const searchSchema = z.object({
  rootValue: z.string().optional(),
})

export const Route = createRootRoute({
  validateSearch: zodValidator(searchSchema),
  search: {
    middlewares: [
      ({ search, next }) => {
        const result = next(search)
        return {
          rootValue: search.rootValue,
          ...result,
        }
      },
    ],
  },
})

Da dieser spezielle Anwendungsfall ziemlich häufig vorkommt, bietet TanStack Router eine generische Implementierung, um Suchparameter über retainSearchParams beizubehalten.

tsx
import { z } from 'zod'
import { createFileRoute, retainSearchParams } from '@tanstack/solid-router'
import { zodValidator } from '@tanstack/zod-adapter'

const searchSchema = z.object({
  rootValue: z.string().optional(),
})

export const Route = createRootRoute({
  validateSearch: zodValidator(searchSchema),
  search: {
    middlewares: [retainSearchParams(['rootValue'])],
  },
})
import { z } from 'zod'
import { createFileRoute, retainSearchParams } from '@tanstack/solid-router'
import { zodValidator } from '@tanstack/zod-adapter'

const searchSchema = z.object({
  rootValue: z.string().optional(),
})

export const Route = createRootRoute({
  validateSearch: zodValidator(searchSchema),
  search: {
    middlewares: [retainSearchParams(['rootValue'])],
  },
})

Ein weiterer gängiger Anwendungsfall ist das Entfernen von Suchparametern aus Links, wenn deren Standardwert festgelegt ist. TanStack Router bietet eine generische Implementierung für diesen Anwendungsfall über stripSearchParams.

tsx
import { z } from 'zod'
import { createFileRoute, stripSearchParams } from '@tanstack/solid-router'
import { zodValidator } from '@tanstack/zod-adapter'

const defaultValues = {
  one: 'abc',
  two: 'xyz',
}

const searchSchema = z.object({
  one: z.string().default(defaultValues.one),
  two: z.string().default(defaultValues.two),
})

export const Route = createFileRoute('/hello')({
  validateSearch: zodValidator(searchSchema),
  search: {
    // strip default values
    middlewares: [stripSearchParams(defaultValues)],
  },
})
import { z } from 'zod'
import { createFileRoute, stripSearchParams } from '@tanstack/solid-router'
import { zodValidator } from '@tanstack/zod-adapter'

const defaultValues = {
  one: 'abc',
  two: 'xyz',
}

const searchSchema = z.object({
  one: z.string().default(defaultValues.one),
  two: z.string().default(defaultValues.two),
})

export const Route = createFileRoute('/hello')({
  validateSearch: zodValidator(searchSchema),
  search: {
    // strip default values
    middlewares: [stripSearchParams(defaultValues)],
  },
})

Mehrere Middlewares können verkettet werden. Das folgende Beispiel zeigt, wie sowohl retainSearchParams als auch stripSearchParams kombiniert werden.

tsx
import {
  Link,
  createFileRoute,
  retainSearchParams,
  stripSearchParams,
} from '@tanstack/solid-router'
import { z } from 'zod'
import { zodValidator } from '@tanstack/zod-adapter'

const defaultValues = ['foo', 'bar']

export const Route = createFileRoute('/search')({
  validateSearch: zodValidator(
    z.object({
      retainMe: z.string().optional(),
      arrayWithDefaults: z.string().array().default(defaultValues),
      required: z.string(),
    }),
  ),
  search: {
    middlewares: [
      retainSearchParams(['retainMe']),
      stripSearchParams({ arrayWithDefaults: defaultValues }),
    ],
  },
})
import {
  Link,
  createFileRoute,
  retainSearchParams,
  stripSearchParams,
} from '@tanstack/solid-router'
import { z } from 'zod'
import { zodValidator } from '@tanstack/zod-adapter'

const defaultValues = ['foo', 'bar']

export const Route = createFileRoute('/search')({
  validateSearch: zodValidator(
    z.object({
      retainMe: z.string().optional(),
      arrayWithDefaults: z.string().array().default(defaultValues),
      required: z.string(),
    }),
  ),
  search: {
    middlewares: [
      retainSearchParams(['retainMe']),
      stripSearchParams({ arrayWithDefaults: defaultValues }),
    ],
  },
})
Unsere Partner
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.

Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.