first commit
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
# ctx-root-context: Define Context at Root Route
|
||||
|
||||
## Priority: LOW
|
||||
|
||||
## Explanation
|
||||
|
||||
Use `createRootRouteWithContext` to define typed context that flows through your entire route tree. This enables dependency injection for things like query clients, auth state, and services.
|
||||
|
||||
## Bad Example
|
||||
|
||||
```tsx
|
||||
// No context - importing globals directly
|
||||
// routes/__root.tsx
|
||||
import { createRootRoute } from '@tanstack/react-router'
|
||||
import { queryClient } from '@/lib/query-client' // Global import
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: RootComponent,
|
||||
})
|
||||
|
||||
// routes/posts.tsx
|
||||
import { queryClient } from '@/lib/query-client' // Import again
|
||||
|
||||
export const Route = createFileRoute('/posts')({
|
||||
loader: async () => {
|
||||
// Using global - harder to test, couples to implementation
|
||||
return queryClient.ensureQueryData(postQueries.list())
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Good Example
|
||||
|
||||
```tsx
|
||||
// routes/__root.tsx
|
||||
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
|
||||
// Define the context interface
|
||||
interface RouterContext {
|
||||
queryClient: QueryClient
|
||||
auth: {
|
||||
user: User | null
|
||||
isAuthenticated: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const Route = createRootRouteWithContext<RouterContext>()({
|
||||
component: RootComponent,
|
||||
})
|
||||
|
||||
function RootComponent() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main>
|
||||
<Outlet />
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// router.tsx - Provide context when creating router
|
||||
import { createRouter } from '@tanstack/react-router'
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
|
||||
import { routeTree } from './routeTree.gen'
|
||||
|
||||
export function getRouter(auth: RouterContext['auth'] = { user: null, isAuthenticated: false }) {
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
context: {
|
||||
queryClient,
|
||||
auth,
|
||||
},
|
||||
defaultPreload: 'intent',
|
||||
defaultPreloadStaleTime: 0,
|
||||
scrollRestoration: true,
|
||||
})
|
||||
|
||||
setupRouterSsrQueryIntegration({ router, queryClient })
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// routes/posts.tsx - Use context in loaders
|
||||
export const Route = createFileRoute('/posts')({
|
||||
loader: async ({ context: { queryClient } }) => {
|
||||
// Context is typed and injected
|
||||
return queryClient.ensureQueryData(postQueries.list())
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Good Example: Auth-Protected Routes
|
||||
|
||||
```tsx
|
||||
// routes/__root.tsx
|
||||
interface RouterContext {
|
||||
queryClient: QueryClient
|
||||
auth: AuthState
|
||||
}
|
||||
|
||||
export const Route = createRootRouteWithContext<RouterContext>()({
|
||||
component: RootComponent,
|
||||
})
|
||||
|
||||
// routes/_authenticated.tsx - Layout route for protected pages
|
||||
export const Route = createFileRoute('/_authenticated')({
|
||||
beforeLoad: async ({ context, location }) => {
|
||||
if (!context.auth.isAuthenticated) {
|
||||
throw redirect({
|
||||
to: '/login',
|
||||
search: { redirect: location.href },
|
||||
})
|
||||
}
|
||||
},
|
||||
component: AuthenticatedLayout,
|
||||
})
|
||||
|
||||
// routes/_authenticated/dashboard.tsx
|
||||
export const Route = createFileRoute('/_authenticated/dashboard')({
|
||||
loader: async ({ context: { queryClient, auth } }) => {
|
||||
// We know user is authenticated from parent beforeLoad
|
||||
return queryClient.ensureQueryData(
|
||||
dashboardQueries.forUser(auth.user!.id)
|
||||
)
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Extending Context with beforeLoad
|
||||
|
||||
```tsx
|
||||
// routes/posts/$postId.tsx
|
||||
export const Route = createFileRoute('/posts/$postId')({
|
||||
beforeLoad: async ({ context, params }) => {
|
||||
// Extend context with route-specific data
|
||||
const post = await fetchPost(params.postId)
|
||||
|
||||
return {
|
||||
post, // Available to this route and children
|
||||
}
|
||||
},
|
||||
loader: async ({ context }) => {
|
||||
// context now includes 'post' from beforeLoad
|
||||
const comments = await fetchComments(context.post.id)
|
||||
return { comments }
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Context vs. Loader Data
|
||||
|
||||
| Context | Loader Data |
|
||||
|---------|-------------|
|
||||
| Available in beforeLoad, loader, and component | Only available in component |
|
||||
| Set at router creation or in beforeLoad | Returned from loader |
|
||||
| Good for services, clients, auth | Good for route-specific data |
|
||||
| Flows down to all children | Specific to route |
|
||||
|
||||
## Context
|
||||
|
||||
- Type the context interface in `createRootRouteWithContext<T>()`
|
||||
- Provide context when calling `createRouter({ context: {...} })`
|
||||
- Context flows from root to all nested routes
|
||||
- Use `beforeLoad` to extend context for specific subtrees
|
||||
- Enables testability - inject mocks in tests
|
||||
- Avoids global imports and singletons
|
||||
Reference in New Issue
Block a user