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

Best Practice

Custom Hooks per le Query

La best practice più importante è wrappare le query in custom hooks. Non usare useQuery direttamente nei componenti:

// ❌ Query inline nel componente
function UserProfile({ userId }: { userId: string }) {
  const { data } = useQuery({
    queryKey: ['users', userId],
    queryFn: () => fetchUser(userId),
  });
  return <div>{data?.name}</div>;
}

// ✅ Custom hook dedicato
function useUser(userId: string) {
  return useQuery({
    queryKey: ['users', userId],
    queryFn: () => fetchUser(userId),
  });
}

function UserProfile({ userId }: { userId: string }) {
  const { data } = useUser(userId);
  return <div>{data?.name}</div>;
}

Questo approccio centralizza la configurazione, rende le query riutilizzabili e semplifica i test.

Query Key Factory

Crea una factory per le query keys per evitare errori e mantenere la coerenza:

export const userKeys = {
  all: ['users'] as const,
  lists: () => [...userKeys.all, 'list'] as const,
  list: (filters: UserFilters) => [...userKeys.lists(), filters] as const,
  details: () => [...userKeys.all, 'detail'] as const,
  detail: (id: string) => [...userKeys.details(), id] as const,
};

// Uso
useQuery({ queryKey: userKeys.detail(userId), ... });

// Invalidazione granulare
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
queryClient.invalidateQueries({ queryKey: userKeys.all });

Configurare staleTime e gcTime

Non lasciare i valori di default per tutto. Configura in base al tipo di dato:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60,     // 1 minuto di default
      gcTime: 1000 * 60 * 5,    // 5 minuti in cache
      retry: 2,
      refetchOnWindowFocus: false,
    },
  },
});

// Override per dati che cambiano raramente
function useCountries() {
  return useQuery({
    queryKey: ['countries'],
    queryFn: fetchCountries,
    staleTime: 1000 * 60 * 60 * 24, // 24 ore
  });
}

// Override per dati real-time
function useNotifications() {
  return useQuery({
    queryKey: ['notifications'],
    queryFn: fetchNotifications,
    staleTime: 0,                    // Sempre stale
    refetchInterval: 30_000,         // Polling ogni 30s
  });
}

Separare Data Layer dalla UI

Organizza il codice separando le query dalla logica dei componenti:

src/
├── api/
│   ├── users.ts           # Funzioni fetch (fetchUsers, createUser...)
│   └── posts.ts
├── hooks/
│   ├── useUsers.ts        # Custom hooks (useUsers, useUser, useCreateUser)
│   └── usePosts.ts
├── keys/
│   └── queryKeys.ts       # Query key factory
└── components/
    └── UserList.tsx        # Componenti (usano solo custom hooks)

Gestione degli Errori

Usa un error handler globale per log e notifiche, e gestisci errori specifici nei componenti:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      throwOnError: (error) => {
        // Lancia solo per errori non gestibili
        return error.status === 401;
      },
    },
    mutations: {
      onError: (error) => {
        // Toast globale per errori mutation
        toast.error(`Errore: ${error.message}`);
      },
    },
  },
});

Quando NON Usare React Query

Non tutto deve essere una query:

  • Stato client-only (tema, sidebar aperta, modale) → usa useState o Zustand
  • Dati statici che non cambiano mai → importali direttamente
  • Form state → usa React Hook Form o librerie specifiche
  • WebSocket/real-time → React Query può funzionare ma valuta alternative dedicate

Evitare il Waterfall

Non creare catene di query dipendenti quando puoi parallelizzare:

// ❌ Waterfall: ogni query aspetta la precedente
const { data: user } = useQuery({ queryKey: ['user', id], ... });
const { data: posts } = useQuery({
  queryKey: ['posts', user?.id],
  enabled: !!user,
  ...
});

// ✅ Se puoi, carica in parallelo o combina nell'API
const { data } = useQuery({
  queryKey: ['user-with-posts', id],
  queryFn: () => fetchUserWithPosts(id), // Un'unica chiamata API
});

Prefetch per UX Migliore

Prefetch i dati prima che l’utente ne abbia bisogno:

// Prefetch on hover
function UserLink({ userId }: { userId: string }) {
  const queryClient = useQueryClient();

  const prefetch = () => {
    queryClient.prefetchQuery({
      queryKey: userKeys.detail(userId),
      queryFn: () => fetchUser(userId),
      staleTime: 1000 * 60 * 5, // Non re-fetchare se < 5min
    });
  };

  return (
    <Link to={`/users/${userId}`} onMouseEnter={prefetch}>
      Profilo utente
    </Link>
  );
}

Confronto con SWR

Feature TanStack Query SWR
DevTools ✅ Ufficiali ❌ Community
Mutations ✅ useMutation ❌ Manuale
Infinite Query ✅ useInfiniteQuery ✅ useSWRInfinite
Optimistic Updates ✅ Built-in ⚠️ Manuale
Suspense SSR ✅ Completo ⚠️ Parziale
Dimensione ~13kb ~4kb
Framework React, Vue, Solid, Svelte Solo React

TanStack Query è la scelta migliore per applicazioni complesse con molte mutation e requisiti di caching avanzati. SWR è più leggero per casi semplici.