149 lines
3.8 KiB
Markdown
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
|