Framework
Version
Integrationen

Router-Kontext

TanStack Router's Router-Kontext ist ein sehr mächtiges Werkzeug, das für Dependency Injection und viele andere Dinge verwendet werden kann. Wie der Name schon sagt, wird der Router-Kontext durch den Router und durch jede übereinstimmende Route weitergegeben. An jeder Route in der Hierarchie kann der Kontext modifiziert oder ergänzt werden. Hier sind einige praktische Anwendungsmöglichkeiten des Router-Kontexts:

  • Dependency Injection
    • Sie können Abhängigkeiten (z.B. eine Loader-Funktion, einen Daten-Fetching-Client, einen Mutationsdienst) bereitstellen, auf die die Route und alle untergeordneten Routen zugreifen und die sie nutzen können, ohne sie direkt zu importieren oder zu erstellen.
  • Breadcrumbs
    • Während das Hauptkontextobjekt für jede Route beim Abstieg zusammengeführt wird, wird auch der eindeutige Kontext jeder Route gespeichert, sodass es möglich ist, Breadcrumbs oder Methoden an den Kontext jeder Route anzuhängen.
  • Dynamische Meta-Tag-Verwaltung
    • Sie können Meta-Tags an den Kontext jeder Route anhängen und dann einen Meta-Tag-Manager verwenden, um die Meta-Tags auf der Seite dynamisch zu aktualisieren, wenn der Benutzer durch die Website navigiert.

Dies sind nur Vorschläge für die Verwendung des Router-Kontexts. Sie können ihn für alles verwenden, was Sie möchten!

Typisierter Router-Kontext

Wie alles andere ist auch der Root-Router-Kontext streng typisiert. Dieser Typ kann über die beforeLoad-Option jeder Route erweitert werden, da er im Routen-Match-Baum nach unten gemergt wird. Um den Typ des Root-Router-Kontexts einzuschränken, müssen Sie die Funktion createRootRouteWithContext<YourContextTypeHere>()(routeOptions) verwenden, um einen neuen Router-Kontext zu erstellen, anstatt die Funktion createRootRoute(), um Ihre Root-Route zu erstellen. Hier ist ein Beispiel

tsx
import {
  createRootRouteWithContext,
  createRouter,
} from '@tanstack/react-router'

interface MyRouterContext {
  user: User
}

// Use the routerContext to create your root route
const rootRoute = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})

const routeTree = rootRoute.addChildren([
  // ...
])

// Use the routerContext to create your router
const router = createRouter({
  routeTree,
})
import {
  createRootRouteWithContext,
  createRouter,
} from '@tanstack/react-router'

interface MyRouterContext {
  user: User
}

// Use the routerContext to create your root route
const rootRoute = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})

const routeTree = rootRoute.addChildren([
  // ...
])

// Use the routerContext to create your router
const router = createRouter({
  routeTree,
})

Übergabe des initialen Router-Kontexts

Der Router-Kontext wird dem Router bei der Instanziierung übergeben. Sie können den initialen Router-Kontext über die context-Option an den Router übergeben.

Tipp

Wenn Ihr Kontext erforderliche Eigenschaften hat, sehen Sie einen TypeScript-Fehler, wenn Sie diese nicht im initialen Router-Kontext übergeben. Wenn alle Ihre Kontext-Eigenschaften optional sind, sehen Sie keinen TypeScript-Fehler und die Übergabe des Kontexts ist optional. Wenn Sie keinen Router-Kontext übergeben, ist der Standardwert {}.

tsx
import { createRouter } from '@tanstack/react-router'

// Use the routerContext you created to create your router
const router = createRouter({
  routeTree,
  context: {
    user: {
      id: '123',
      name: 'John Doe',
    },
  },
})
import { createRouter } from '@tanstack/react-router'

// Use the routerContext you created to create your router
const router = createRouter({
  routeTree,
  context: {
    user: {
      id: '123',
      name: 'John Doe',
    },
  },
})

Ungültigmachen des Router-Kontexts

Wenn Sie den an den Router übergebenen Kontextzustand ungültig machen müssen, können Sie die Methode invalidate aufrufen, um dem Router mitzuteilen, den Kontext neu zu berechnen. Dies ist nützlich, wenn Sie den Kontextzustand aktualisieren und möchten, dass der Router den Kontext für alle Routen neu berechnet.

tsx
function useAuth() {
  const router = useRouter()
  const [user, setUser] = useState<User | null>(null)

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((user) => {
      setUser(user)
      router.invalidate()
    })

    return unsubscribe
  }, [])

  return user
}
function useAuth() {
  const router = useRouter()
  const [user, setUser] = useState<User | null>(null)

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((user) => {
      setUser(user)
      router.invalidate()
    })

    return unsubscribe
  }, [])

  return user
}

Verwendung des Router-Kontexts

Nachdem Sie den Typ des Router-Kontexts definiert haben, können Sie ihn in Ihren Routendefinitionen verwenden.

tsx
// src/routes/todos.tsx
export const Route = createFileRoute('/todos')({
  component: Todos,
  loader: ({ context }) => fetchTodosByUserId(context.user.id),
})
// src/routes/todos.tsx
export const Route = createFileRoute('/todos')({
  component: Todos,
  loader: ({ context }) => fetchTodosByUserId(context.user.id),
})

Sie können sogar Daten-Fetching- und Mutationsimplementierungen selbst injizieren! Tatsächlich ist dies sehr zu empfehlen 😜

Probieren wir dies mit einer einfachen Funktion zum Abrufen einiger Todos aus.

tsx
const fetchTodosByUserId = async ({ userId }) => {
  const response = await fetch(`/api/todos?userId=${userId}`)
  const data = await response.json()
  return data
}

const router = createRouter({
  routeTree: rootRoute,
  context: {
    userId: '123',
    fetchTodosByUserId,
  },
})
const fetchTodosByUserId = async ({ userId }) => {
  const response = await fetch(`/api/todos?userId=${userId}`)
  const data = await response.json()
  return data
}

const router = createRouter({
  routeTree: rootRoute,
  context: {
    userId: '123',
    fetchTodosByUserId,
  },
})

Dann, in Ihrer Route

tsx
// src/routes/todos.tsx
export const Route = createFileRoute('/todos')({
  component: Todos,
  loader: ({ context }) => context.fetchTodosByUserId(context.userId),
})
// src/routes/todos.tsx
export const Route = createFileRoute('/todos')({
  component: Todos,
  loader: ({ context }) => context.fetchTodosByUserId(context.userId),
})

Wie wäre es mit einer externen Daten-Fetching-Bibliothek?

tsx
import {
  createRootRouteWithContext,
  createRouter,
} from '@tanstack/react-router'

interface MyRouterContext {
  queryClient: QueryClient
}

const rootRoute = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})

const queryClient = new QueryClient()

const router = createRouter({
  routeTree: rootRoute,
  context: {
    queryClient,
  },
})
import {
  createRootRouteWithContext,
  createRouter,
} from '@tanstack/react-router'

interface MyRouterContext {
  queryClient: QueryClient
}

const rootRoute = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})

const queryClient = new QueryClient()

const router = createRouter({
  routeTree: rootRoute,
  context: {
    queryClient,
  },
})

Dann, in Ihrer Route

tsx
// src/routes/todos.tsx
export const Route = createFileRoute('/todos')({
  component: Todos,
  loader: async ({ context }) => {
    await context.queryClient.ensureQueryData({
      queryKey: ['todos', { userId: user.id }],
      queryFn: fetchTodos,
    })
  },
})
// src/routes/todos.tsx
export const Route = createFileRoute('/todos')({
  component: Todos,
  loader: async ({ context }) => {
    await context.queryClient.ensureQueryData({
      queryKey: ['todos', { userId: user.id }],
      queryFn: fetchTodos,
    })
  },
})

Wie wäre es mit React Context/Hooks?

Bei dem Versuch, React Context oder Hooks in den beforeLoad- oder loader-Funktionen Ihrer Route zu verwenden, ist es wichtig, die Hooks-Regeln von React zu beachten. Sie können Hooks nicht in einer Nicht-React-Funktion verwenden, daher können Sie Hooks nicht in Ihren beforeLoad- oder loader-Funktionen verwenden.

Wie verwenden wir also React Context oder Hooks in den beforeLoad- oder loader-Funktionen unserer Route? Wir können den Router-Kontext verwenden, um den React Context oder Hooks an die beforeLoad- oder loader-Funktionen unserer Route weiterzugeben.

Betrachten wir die Einrichtung eines Beispiels, bei dem wir einen useNetworkStrength-Hook an die loader-Funktion unserer Route weitergeben.

  • src/routes/__root.tsx
tsx
// First, make sure the context for the root route is typed
import { createRootRouteWithContext } from '@tanstack/react-router'
import { useNetworkStrength } from '@/hooks/useNetworkStrength'

interface MyRouterContext {
  networkStrength: ReturnType<typeof useNetworkStrength>
}

export const Route = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})
// First, make sure the context for the root route is typed
import { createRootRouteWithContext } from '@tanstack/react-router'
import { useNetworkStrength } from '@/hooks/useNetworkStrength'

interface MyRouterContext {
  networkStrength: ReturnType<typeof useNetworkStrength>
}

export const Route = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})

In diesem Beispiel würden wir den Hook vor dem Rendern des Routers mit der <RouterProvider /> instanziieren. Auf diese Weise würde der Hook in React-Land aufgerufen und somit die Hooks-Regeln eingehalten.

  • src/router.tsx
tsx
import { createRouter } from '@tanstack/react-router'

import { routeTree } from './routeTree.gen'

export const router = createRouter({
  routeTree,
  context: {
    networkStrength: undefined!, // We'll set this in React-land
  },
})
import { createRouter } from '@tanstack/react-router'

import { routeTree } from './routeTree.gen'

export const router = createRouter({
  routeTree,
  context: {
    networkStrength: undefined!, // We'll set this in React-land
  },
})
  • src/main.tsx
tsx
import { RouterProvider } from '@tanstack/react-router'
import { router } from './router'

import { useNetworkStrength } from '@/hooks/useNetworkStrength'

function App() {
  const networkStrength = useNetworkStrength()
  // Inject the returned value from the hook into the router context
  return <RouterProvider router={router} context={{ networkStrength }} />
}

// ...
import { RouterProvider } from '@tanstack/react-router'
import { router } from './router'

import { useNetworkStrength } from '@/hooks/useNetworkStrength'

function App() {
  const networkStrength = useNetworkStrength()
  // Inject the returned value from the hook into the router context
  return <RouterProvider router={router} context={{ networkStrength }} />
}

// ...

Nun können wir in der loader-Funktion unserer Route auf den networkStrength-Hook aus dem Router-Kontext zugreifen.

  • src/routes/posts.tsx
tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts')({
  component: Posts,
  loader: ({ context }) => {
    if (context.networkStrength === 'STRONG') {
      // Do something
    }
  },
})
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts')({
  component: Posts,
  loader: ({ context }) => {
    if (context.networkStrength === 'STRONG') {
      // Do something
    }
  },
})

Modifizieren des Router-Kontexts

Der Router-Kontext wird den Routenbaum hinuntergegeben und an jeder Route zusammengeführt. Das bedeutet, dass Sie den Kontext an jeder Route modifizieren können und die Modifikationen für alle untergeordneten Routen verfügbar sind. Hier ist ein Beispiel

  • src/routes/__root.tsx
tsx
import { createRootRouteWithContext } from '@tanstack/react-router'

interface MyRouterContext {
  foo: boolean
}

export const Route = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})
import { createRootRouteWithContext } from '@tanstack/react-router'

interface MyRouterContext {
  foo: boolean
}

export const Route = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})
  • src/router.tsx
tsx
import { createRouter } from '@tanstack/react-router'

import { routeTree } from './routeTree.gen'

const router = createRouter({
  routeTree,
  context: {
    foo: true,
  },
})
import { createRouter } from '@tanstack/react-router'

import { routeTree } from './routeTree.gen'

const router = createRouter({
  routeTree,
  context: {
    foo: true,
  },
})
  • src/routes/todos.tsx
tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/todos')({
  component: Todos,
  beforeLoad: () => {
    return {
      bar: true,
    }
  },
  loader: ({ context }) => {
    context.foo // true
    context.bar // true
  },
})
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/todos')({
  component: Todos,
  beforeLoad: () => {
    return {
      bar: true,
    }
  },
  loader: ({ context }) => {
    context.foo // true
    context.bar // true
  },
})

Verarbeitung des angesammelten Routen-Kontexts

Kontexte, insbesondere die isolierten Routen-context-Objekte, machen es trivial, die Routenkontext-Objekte für alle übereinstimmenden Routen zu sammeln und zu verarbeiten. Hier ist ein Beispiel, bei dem wir alle übereinstimmenden Routenkontexte verwenden, um eine Breadcrumb-Leiste zu generieren.

tsx
// src/routes/__root.tsx
export const Route = createRootRoute({
  component: () => {
    const matches = useRouterState({ select: (s) => s.matches })

    const breadcrumbs = matches
      .filter((match) => match.context.getTitle)
      .map(({ pathname, context }) => {
        return {
          title: context.getTitle(),
          path: pathname,
        }
      })

    // ...
  },
})
// src/routes/__root.tsx
export const Route = createRootRoute({
  component: () => {
    const matches = useRouterState({ select: (s) => s.matches })

    const breadcrumbs = matches
      .filter((match) => match.context.getTitle)
      .map(({ pathname, context }) => {
        return {
          title: context.getTitle(),
          path: pathname,
        }
      })

    // ...
  },
})

Mit demselben Routenkontext könnten wir auch einen Titel-Tag für den <head> unserer Seite generieren.

tsx
// src/routes/__root.tsx
export const Route = createRootRoute({
  component: () => {
    const matches = useRouterState({ select: (s) => s.matches })

    const matchWithTitle = [...matches]
      .reverse()
      .find((d) => d.context.getTitle)

    const title = matchWithTitle?.context.getTitle() || 'My App'

    return (
      <html>
        <head>
          <title>{title}</title>
        </head>
        <body>{/* ... */}</body>
      </html>
    )
  },
})
// src/routes/__root.tsx
export const Route = createRootRoute({
  component: () => {
    const matches = useRouterState({ select: (s) => s.matches })

    const matchWithTitle = [...matches]
      .reverse()
      .find((d) => d.context.getTitle)

    const title = matchWithTitle?.context.getTitle() || 'My App'

    return (
      <html>
        <head>
          <title>{title}</title>
        </head>
        <body>{/* ... */}</body>
      </html>
    )
  },
})
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.