first commit
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user