mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-03 19:37:52 +00:00
- 16 cross-shell utilities converted to sh/bash chezmoi executables: ai c2p cp-appid extract_agents fab gdc gh-sonr-org-migrate ghg-clone ghn gho git-summary launch_omp_livediff_overlay lc-elir mdcp mdp pj (ghg-clone not named ghg: unrelated cargo binary owns that name) - cwd/history helpers stay per-shell: yy c cd clip-last + ghg/ghn/gho cd-wrappers in bash/functions/ and zsh/functions/, loaded by rc files - fish keeps only shell-state functions; converted ones now hit PATH - cp-appid ignore gating moved to .local/bin path - dot_agents/skills: 82 skills materialized from dotstate storage so dot_claude skill symlinks resolve on fresh bootstrap; dotstate profile/symlinks.json released .agents/skills .bashrc .zshrc
4.6 KiB
4.6 KiB
cache-single-source: Let TanStack Query Manage Caching
Priority: MEDIUM
Explanation
When using TanStack Router with TanStack Query, let Query be the single source of truth for caching. Disable Router's built-in cache with defaultPreloadStaleTime: 0 to avoid confusion about which cache is authoritative.
Bad Example
// Both Router and Query caching enabled - confusing
const router = createRouter({
routeTree,
context: { queryClient },
// Default router caching enabled
// defaultPreloadStaleTime: 30000 (default)
})
export const Route = createFileRoute('/posts')({
loader: async () => {
// Fetches directly - cached by Router
const posts = await fetchPosts()
return { posts }
},
component: PostsPage,
})
function PostsPage() {
// Also uses Query cache - which is authoritative?
const { data } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
})
// Now there are TWO caches with potentially different data
}
Good Example
// router.tsx - Disable router cache when using Query
import { QueryClient } from '@tanstack/react-query'
import { createRouter } from '@tanstack/react-router'
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
import { routeTree } from './routeTree.gen'
export function getRouter() {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 2, // 2 minutes
refetchOnWindowFocus: false,
},
},
})
const router = createRouter({
routeTree,
context: { queryClient },
defaultPreload: 'intent',
defaultPreloadStaleTime: 0, // Let Query manage caching
scrollRestoration: true,
})
setupRouterSsrQueryIntegration({
router,
queryClient,
})
return router
}
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
loader: async ({ context: { queryClient } }) => {
// Query is the single cache source
await queryClient.ensureQueryData(postQueries.all())
// No return needed - data lives in Query cache
},
component: PostsPage,
})
function PostsPage() {
// Single source of truth
const { data: posts } = useSuspenseQuery(postQueries.all())
return <PostList posts={posts} />
}
Cache Comparison
| Feature | Router Cache | Query Cache |
|---|---|---|
| Invalidation | Manual/time-based | Query keys, patterns |
| Background refetch | No | Yes |
| Optimistic updates | No | Yes |
| Mutations | No built-in | Full support |
| DevTools | Limited | Rich debugging |
| Cross-route sharing | Full | Full |
Good Example: Coordinated Caching Config
// router.tsx
export function getRouter() {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // Fresh for 1 minute
gcTime: 10 * 60 * 1000, // Cache for 10 minutes
refetchOnWindowFocus: true, // Refetch when tab focused
retry: 1,
},
},
})
const router = createRouter({
routeTree,
context: { queryClient },
defaultPreload: 'intent',
defaultPreloadStaleTime: 0, // Router defers to Query
scrollRestoration: true,
defaultStructuralSharing: true,
})
setupRouterSsrQueryIntegration({
router,
queryClient,
})
return router
}
Good Example: Preload Still Works
// Preloading still works - it just uses Query's cache
export function getRouter() {
const queryClient = new QueryClient()
const router = createRouter({
routeTree,
context: { queryClient },
defaultPreload: 'intent', // Preload on hover
defaultPreloadStaleTime: 0, // Query decides if data is stale
})
setupRouterSsrQueryIntegration({ router, queryClient })
return router
}
// When user hovers a Link:
// 1. Router triggers preload
// 2. Loader runs ensureQueryData
// 3. Query checks its cache - fresh? skip fetch. stale? refetch.
// 4. User clicks - data already in Query cache
Mutation Invalidation
// Mutations properly invalidate the single cache
const createPost = useMutation({
mutationFn: submitPost,
onSuccess: () => {
// Invalidate Query cache - the single source
queryClient.invalidateQueries({ queryKey: ['posts'] })
// Router automatically uses updated cache on next navigation
navigate({ to: '/posts' })
},
})
Context
defaultPreloadStaleTime: 0means "always ask Query"- Query's staleTime/gcTime controls caching behavior
- Preloading still works - just uses Query's cache
- Mutations, optimistic updates, invalidation all work normally
- DevTools show the single authoritative cache state
- Use
setupRouterSsrQueryIntegrationfor SSR hydration