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.8 KiB
4.8 KiB
flow-loader-query-pattern: Use Loaders with ensureQueryData
Priority: HIGH
Explanation
The recommended pattern combines TanStack Router's loaders with TanStack Query's ensureQueryData. Loaders prefetch data during navigation, while Query manages caching and updates. Use useSuspenseQuery in components since data is guaranteed.
Bad Example
// Only using Query in component - loading waterfall
function PostsPage() {
const { data, isLoading } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
})
if (isLoading) return <Loading />
return <PostList posts={data} />
}
// Only using loader - no cache management
export const Route = createFileRoute('/posts')({
loader: async () => {
const posts = await fetchPosts() // Not cached
return { posts }
},
})
Good Example
// lib/queries/posts.ts - Define queryOptions
import { queryOptions } from '@tanstack/react-query'
export const postQueries = {
all: () => queryOptions({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 5 * 60 * 1000,
}),
detail: (id: string) => queryOptions({
queryKey: ['posts', id],
queryFn: () => fetchPost(id),
staleTime: 5 * 60 * 1000,
}),
}
// routes/posts.tsx
import { createFileRoute } from '@tanstack/react-router'
import { useSuspenseQuery } from '@tanstack/react-query'
import { postQueries } from '@/lib/queries/posts'
export const Route = createFileRoute('/posts')({
loader: async ({ context: { queryClient } }) => {
// Prefetch in loader - runs during navigation
await queryClient.ensureQueryData(postQueries.all())
},
component: PostsPage,
})
function PostsPage() {
// Data guaranteed by loader - no loading state needed
const { data: posts } = useSuspenseQuery(postQueries.all())
return <PostList posts={posts} />
}
// routes/posts/$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params, context: { queryClient } }) => {
await queryClient.ensureQueryData(postQueries.detail(params.postId))
},
component: PostDetailPage,
})
function PostDetailPage() {
const { postId } = Route.useParams()
const { data: post } = useSuspenseQuery(postQueries.detail(postId))
return <PostContent post={post} />
}
Good Example: Parallel Data Loading
export const Route = createFileRoute('/dashboard')({
loader: async ({ context: { queryClient } }) => {
// Load multiple queries in parallel
await Promise.all([
queryClient.ensureQueryData(statsQueries.overview()),
queryClient.ensureQueryData(activityQueries.recent()),
queryClient.ensureQueryData(userQueries.current()),
])
},
component: DashboardPage,
})
function DashboardPage() {
// All data ready - no loading states
const { data: stats } = useSuspenseQuery(statsQueries.overview())
const { data: activity } = useSuspenseQuery(activityQueries.recent())
const { data: user } = useSuspenseQuery(userQueries.current())
return (
<Dashboard
stats={stats}
activity={activity}
user={user}
/>
)
}
Good Example: Optional Prefetch with Non-Critical Data
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params, context: { queryClient } }) => {
// Critical data - await it
await queryClient.ensureQueryData(postQueries.detail(params.postId))
// Non-critical - prefetch but don't await
queryClient.prefetchQuery(postQueries.comments(params.postId))
queryClient.prefetchQuery(postQueries.related(params.postId))
},
component: PostPage,
})
function PostPage() {
const { postId } = Route.useParams()
// Critical - guaranteed by loader
const { data: post } = useSuspenseQuery(postQueries.detail(postId))
// Non-critical - may still be loading
const { data: comments, isLoading: commentsLoading } = useQuery(
postQueries.comments(postId)
)
return (
<article>
<PostContent post={post} />
{commentsLoading ? <CommentsSkeleton /> : <Comments data={comments} />}
</article>
)
}
Data Flow Summary
Navigation Starts
↓
Router matches route
↓
loader() executes
↓
ensureQueryData() checks cache
↓
Fresh cache? → Return cached Stale/missing? → Fetch and cache
↓ ↓
Route renders Route renders
↓ ↓
useSuspenseQuery returns data useSuspenseQuery returns data
Context
ensureQueryDatarespects staleTime - won't refetch fresh datauseSuspenseQuerythrows promise to Suspense if data missing- Loaders enable preloading on link hover
- This pattern eliminates loading waterfalls
- Use
useQueryfor non-critical data that can load after render - Query invalidation and background updates still work normally