00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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>
  )
}