Query Parallele e Dipendenti
Query Parallele
Quando hai bisogno di dati da piu’ endpoint contemporaneamente, le query dichiarate nello stesso componente vengono eseguite in parallelo automaticamente:
function Dashboard() {
const usersQuery = useQuery({ queryKey: ['users'], queryFn: fetchUsers })
const postsQuery = useQuery({ queryKey: ['posts'], queryFn: fetchPosts })
const statsQuery = useQuery({ queryKey: ['stats'], queryFn: fetchStats })
if (usersQuery.isPending || postsQuery.isPending || statsQuery.isPending) {
return <Spinner />
}
return (
<div>
<UserList users={usersQuery.data} />
<PostList posts={postsQuery.data} />
<StatsPanel stats={statsQuery.data} />
</div>
)
}
useQueries per Query Dinamiche
Quando il numero di query non e’ noto a compile time (es. una lista di ID), usa useQueries:
import { useQueries } from '@tanstack/react-query'
function UserProfiles({ userIds }: { userIds: number[] }) {
const userQueries = useQueries({
queries: userIds.map((id) => ({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
staleTime: 1000 * 60 * 5,
})),
})
const isLoading = userQueries.some((q) => q.isPending)
const hasError = userQueries.some((q) => q.isError)
if (isLoading) return <Spinner />
if (hasError) return <p>Errore nel caricamento di alcuni profili</p>
return (
<div>
{userQueries.map((query, index) => (
<UserCard key={userIds[index]} user={query.data!} />
))}
</div>
)
}
Combinare i Risultati
Puoi usare l’opzione combine di useQueries per aggregare i risultati:
function useMultipleUsers(userIds: number[]) {
return useQueries({
queries: userIds.map((id) => ({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
})),
combine: (results) => {
return {
data: results.map((r) => r.data).filter(Boolean),
isPending: results.some((r) => r.isPending),
errors: results.filter((r) => r.isError).map((r) => r.error),
}
},
})
}
// Uso semplificato
function UserList({ ids }: { ids: number[] }) {
const { data, isPending, errors } = useMultipleUsers(ids)
if (isPending) return <Spinner />
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
Query Dipendenti (Waterfall)
Quando una query dipende dal risultato di un’altra, usa l’opzione enabled per creare una catena:
function UserPosts({ userId }: { userId: number }) {
// Prima query: carica l'utente
const userQuery = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
})
// Seconda query: carica i post SOLO quando abbiamo l'utente
const postsQuery = useQuery({
queryKey: ['posts', { authorId: userQuery.data?.id }],
queryFn: () => fetchPostsByAuthor(userQuery.data!.id),
enabled: !!userQuery.data, // si attiva solo quando l'utente e' stato caricato
})
if (userQuery.isPending) return <p>Caricamento utente...</p>
if (userQuery.isError) return <p>Errore: {userQuery.error.message}</p>
return (
<div>
<h2>{userQuery.data.name}</h2>
{postsQuery.isPending ? (
<p>Caricamento post...</p>
) : postsQuery.isError ? (
<p>Errore nel caricamento dei post</p>
) : (
<ul>
{postsQuery.data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)}
</div>
)
}
Catena di Tre Query
function OrderDetails({ orderId }: { orderId: string }) {
const orderQuery = useQuery({
queryKey: ['order', orderId],
queryFn: () => fetchOrder(orderId),
})
const customerQuery = useQuery({
queryKey: ['customer', orderQuery.data?.customerId],
queryFn: () => fetchCustomer(orderQuery.data!.customerId),
enabled: !!orderQuery.data?.customerId,
})
const addressQuery = useQuery({
queryKey: ['address', customerQuery.data?.addressId],
queryFn: () => fetchAddress(customerQuery.data!.addressId),
enabled: !!customerQuery.data?.addressId,
})
return (
<div>
<h2>Ordine #{orderId}</h2>
{orderQuery.isPending && <p>Caricamento ordine...</p>}
{customerQuery.isPending && <p>Caricamento cliente...</p>}
{addressQuery.isPending && <p>Caricamento indirizzo...</p>}
{addressQuery.data && (
<p>Spedizione a: {addressQuery.data.city}, {addressQuery.data.street}</p>
)}
</div>
)
}
Suspense con Multiple Queries
Con useSuspenseQuery puoi semplificare la gestione del loading, delegandola al Suspense boundary:
import { useSuspenseQuery, useSuspenseQueries } from '@tanstack/react-query'
import { Suspense } from 'react'
function DashboardContent() {
// Tutte le query in parallelo con Suspense
const [users, posts] = useSuspenseQueries({
queries: [
{ queryKey: ['users'], queryFn: fetchUsers },
{ queryKey: ['posts'], queryFn: fetchPosts },
],
})
// Nessun check isPending necessario - Suspense gestisce il loading
return (
<div>
<p>Utenti: {users.data.length}</p>
<p>Post: {posts.data.length}</p>
</div>
)
}
function Dashboard() {
return (
<Suspense fallback={<Spinner />}>
<DashboardContent />
</Suspense>
)
}
Suspense Granulare
Per mostrare loading separati per sezioni diverse, usa piu’ Suspense boundary:
function Dashboard() {
return (
<div>
<Suspense fallback={<p>Caricamento utenti...</p>}>
<UserSection />
</Suspense>
<Suspense fallback={<p>Caricamento post...</p>}>
<PostSection />
</Suspense>
</div>
)
}