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
useStateo 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.