Prefetching
Perche’ Fare Prefetching
Il prefetching consente di caricare i dati prima che l’utente ne abbia bisogno. Quando poi naviga verso una pagina, i dati sono gia’ in cache e la UI appare istantaneamente.
queryClient.prefetchQuery
Il metodo piu’ diretto per precaricare dati:
import { useQueryClient } from '@tanstack/react-query'
function PostList({ posts }: { posts: Post[] }) {
const queryClient = useQueryClient()
return (
<ul>
{posts.map((post) => (
<li
key={post.id}
onMouseEnter={() => {
// Precarica il dettaglio quando l'utente passa sopra col mouse
queryClient.prefetchQuery({
queryKey: ['post', post.id],
queryFn: () => fetchPost(post.id),
staleTime: 1000 * 60 * 5, // considera fresco per 5 minuti
})
}}
>
<Link to={`/posts/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
Prefetch su Hover
Un pattern molto comune e’ precaricare quando l’utente passa il mouse su un link:
function NavLink({ to, queryKey, queryFn, children }: NavLinkProps) {
const queryClient = useQueryClient()
const handleMouseEnter = () => {
queryClient.prefetchQuery({
queryKey,
queryFn,
staleTime: 1000 * 60, // 1 minuto
})
}
return (
<Link to={to} onMouseEnter={handleMouseEnter}>
{children}
</Link>
)
}
// Uso
<NavLink
to="/dashboard"
queryKey={['dashboard', 'stats']}
queryFn={fetchDashboardStats}
>
Dashboard
</NavLink>
Prefetch su Route Change
Con React Router puoi precaricare dati durante la navigazione usando i loader:
import { QueryClient } from '@tanstack/react-query'
import { createBrowserRouter } from 'react-router-dom'
const queryClient = new QueryClient()
const router = createBrowserRouter([
{
path: '/posts/:postId',
loader: async ({ params }) => {
// Precarica (o usa dalla cache) il post
await queryClient.ensureQueryData({
queryKey: ['post', Number(params.postId)],
queryFn: () => fetchPost(Number(params.postId)),
staleTime: 1000 * 60,
})
return null
},
element: <PostDetail />,
},
])
La differenza tra prefetchQuery e ensureQueryData:
- prefetchQuery: Lancia il fetch ma non aspetta il risultato. Non lancia errori.
- ensureQueryData: Aspetta il risultato e lo ritorna. Utile nei loader.
initialData vs placeholderData
Sono due strategie diverse per mostrare dati prima che il fetch completi:
initialData
Dati “reali” che vengono messi in cache. Utili quando hai gia’ i dati da un’altra query:
function PostDetail({ postId }: { postId: number }) {
const { data } = useQuery({
queryKey: ['post', postId],
queryFn: () => fetchPost(postId),
initialData: () => {
// Cerca il post nella cache della lista
const posts = queryClient.getQueryData<Post[]>(['posts'])
return posts?.find((p) => p.id === postId)
},
// Quando i dati iniziali sono stati messi in cache
initialDataUpdatedAt: () => {
return queryClient.getQueryState(['posts'])?.dataUpdatedAt
},
})
return <h1>{data?.title}</h1>
}
placeholderData
Dati “finti” che non vengono messi in cache. Mostrati solo durante il loading:
function PostDetail({ postId }: { postId: number }) {
const { data, isPlaceholderData } = useQuery({
queryKey: ['post', postId],
queryFn: () => fetchPost(postId),
placeholderData: (previousData) => {
// Mostra i dati del post precedente mentre carica il nuovo
return previousData
},
})
return (
<div style={{ opacity: isPlaceholderData ? 0.5 : 1 }}>
<h1>{data?.title}</h1>
</div>
)
}
Confronto
| Caratteristica | initialData | placeholderData |
|---|---|---|
| Viene messa in cache | Si | No |
| Influenza staleTime | Si | No |
| isPlaceholderData | Sempre false | true durante il caricamento |
| Uso tipico | Dati da un’altra query | Dati finti o precedenti |
Hydration per SSR
Per il Server-Side Rendering, puoi precaricare i dati sul server e passarli al client tramite dehydrate/hydrate:
// server.tsx (o getServerSideProps in Next.js Pages Router)
import { dehydrate, QueryClient } from '@tanstack/react-query'
export async function getServerSideProps() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
})
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
}
// _app.tsx
import { HydrationBoundary } from '@tanstack/react-query'
function MyApp({ Component, pageProps }: AppProps) {
const [queryClient] = useState(() => new QueryClient())
return (
<QueryClientProvider client={queryClient}>
<HydrationBoundary state={pageProps.dehydratedState}>
<Component {...pageProps} />
</HydrationBoundary>
</QueryClientProvider>
)
}
Prefetching di Infinite Queries
Puoi anche precaricare query infinite:
await queryClient.prefetchInfiniteQuery({
queryKey: ['posts', 'infinite'],
queryFn: ({ pageParam }) => fetchPostsPage(pageParam),
initialPageParam: 0,
pages: 3, // precarica le prime 3 pagine
getNextPageParam: (lastPage) => lastPage.nextCursor,
})