Capítulo 49 de 57

Chapter 49: Router Context

Core Idea

Router context is hierarchical dependency injection built into TanStack Router: a typed context object flows from the root route down through every matched route, can be extended at each route via beforeLoad, and is the recommended way to inject services (data clients, auth state, hooks) into loaders without direct imports.

Key Concepts

  • createRootRouteWithContext<T>(): Factory that types the root router context, requiring the router's context option to satisfy T at instantiation.
  • Initial context: Passed to createRouter({ context: {...} }); required properties cause a TypeScript error if omitted, optional-only context defaults to {}.
  • Context merging: Each route's beforeLoad can return additional context fields that merge into the context seen by that route and all its children (not siblings or ancestors).
  • router.invalidate(): Forces the router to recompute context for all routes, used e.g. after external auth state changes to refresh route data.
  • Dependency injection pattern: Injecting things like a queryClient or a fetchTodosByUserId function into context so loaders can call context.queryClient... without importing it directly.
  • React hooks in context: Since beforeLoad/loader are not React components, hooks can't be called there directly; instead, call the hook in a React component (e.g. App) and pass its result into <RouterProvider context={{ ... }} />, with the router's initial context stubbed as undefined!.
  • Accumulated match context: useRouterState({ select: (s) => s.matches }) lets you read every matched route's own context object, useful for building breadcrumbs or dynamic <title> tags from route-level metadata like context.getTitle.

Code Examples

interface MyRouterContext {
  user: User
}

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

const router = createRouter({
  routeTree: rootRoute.addChildren([/* ... */]),
  context: {
    user: { id: '123', name: 'John Doe' },
  },
})
  • What it demonstrates: Typing and supplying the root router context at creation time.
export const Route = createFileRoute('/todos')({
  component: Todos,
  beforeLoad: () => ({ bar: true }),
  loader: ({ context }) => {
    context.foo // true (from root)
    context.bar // true (added by this route's beforeLoad)
  },
})
  • What it demonstrates: A route's beforeLoad extending context for itself and its descendants.

Key Takeaways

  1. Prefer router context over direct imports for cross-cutting dependencies (data clients, auth, feature flags); it's automatically available to every descendant route's beforeLoad/loader.
  2. React hooks cannot run inside beforeLoad/loader (Rules of Hooks); bridge them by calling the hook in a component and passing the result through <RouterProvider context={...}>.
  3. Call router.invalidate() when externally-tracked context state (e.g. an auth listener) changes, to force loaders to re-run with fresh context.
  4. Each matched route retains its own unique context slice, which is queryable via useRouterState, making it a natural source for breadcrumbs or per-page <title> tags.

Connects To

  • Ch 47: Type Safety, explains the createRootRouteWithContext typing mechanism in more depth.
  • Ch 51: Authenticated Routes, uses beforeLoad and context for auth checks.
  • Ch 53: TanStack Query Integration, commonly injects queryClient via router context.