Files
dotfiles/dot_agents/skills/tanstack-router-best-practices/rules/load-use-loaders.md
T
Prad Nukala 6406208b35 shell: fish functions -> ~/.local/bin executables + bash/zsh ports; manage ~/.agents
- 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
2026-08-10 15:15:06 -04:00

149 lines
3.8 KiB
Markdown

# load-use-loaders: Use Route Loaders for Data Fetching
## Priority: HIGH
## Explanation
Route loaders execute before the route renders, enabling data to be ready when the component mounts. This prevents loading waterfalls, enables preloading, and integrates with the router's caching layer.
## Bad Example
```tsx
// Fetching in component - creates waterfall
function PostsPage() {
const [posts, setPosts] = useState<Post[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
// Route renders, THEN data fetches, THEN UI updates
fetchPosts().then((data) => {
setPosts(data)
setLoading(false)
})
}, [])
if (loading) return <Loading />
return <PostList posts={posts} />
}
// No preloading possible - user sees loading state on navigation
```
## Good Example
```tsx
// routes/posts.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts')({
loader: async () => {
const posts = await fetchPosts()
return { posts }
},
component: PostsPage,
})
function PostsPage() {
// Data is ready when component mounts - no loading state needed
const { posts } = Route.useLoaderData()
return <PostList posts={posts} />
}
```
## Good Example: With Parameters
```tsx
// routes/posts/$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
// params are type-safe and guaranteed to exist
const post = await fetchPost(params.postId)
const comments = await fetchComments(params.postId)
return { post, comments }
},
component: PostDetailPage,
})
function PostDetailPage() {
const { post, comments } = Route.useLoaderData()
const { postId } = Route.useParams()
return (
<article>
<h1>{post.title}</h1>
<PostContent content={post.content} />
<CommentList comments={comments} />
</article>
)
}
```
## Good Example: With TanStack Query
```tsx
// routes/posts/$postId.tsx
import { queryOptions } from '@tanstack/react-query'
const postQueryOptions = (postId: string) =>
queryOptions({
queryKey: ['posts', postId],
queryFn: () => fetchPost(postId),
})
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params, context: { queryClient } }) => {
// Ensure data is in cache before render
await queryClient.ensureQueryData(postQueryOptions(params.postId))
},
component: PostDetailPage,
})
function PostDetailPage() {
const { postId } = Route.useParams()
// useSuspenseQuery because loader guarantees data exists
const { data: post } = useSuspenseQuery(postQueryOptions(postId))
return <PostContent post={post} />
}
```
## Loader Context Properties
```tsx
export const Route = createFileRoute('/posts')({
loader: async ({
params, // Route path parameters
context, // Route context (queryClient, auth, etc.)
abortController, // For cancelling stale requests
cause, // 'enter' | 'preload' | 'stay'
deps, // Dependencies from loaderDeps
preload, // Boolean: true if preloading
}) => {
// Use abortController for fetch cancellation
const response = await fetch('/api/posts', {
signal: abortController.signal,
})
// Different behavior for preload vs navigation
if (preload) {
// Lighter data for preload
return { posts: await response.json() }
}
// Full data for actual navigation
const posts = await response.json()
const stats = await fetchStats()
return { posts, stats }
},
})
```
## Context
- Loaders run during route matching, before component render
- Supports parallel loading across nested routes
- Enables preloading on link hover/focus
- Built-in stale-while-revalidate caching
- For complex caching needs, integrate with TanStack Query
- Use `beforeLoad` for auth checks and redirects