f in x
React Query — Data Fetching Caching e Server State per App React Che Non Perdono Dati
> cd .. / HUB_EDITORIALE > Visualizza in Inglese
Sviluppo di siti web

React Query — Data Fetching Caching e Server State per App React Che Non Perdono Dati

[2026-07-12] Author: Ing. Calogero Bono
Zenithby Meteora Web Il sistema operativo della tua attività. Social, clienti, prenotazioni e fatture in un'unica piattaforma. Palestre, barber, professionisti. Scopri Zenith Demo gratis · senza carta

Ogni volta che scrivi useEffect per fare una chiamata API, stai costruendo un castello di carte. Gestione loading, errori, cache, refetch manuale, paginazione — tutto a mano, con mille useState e un codice che si allunga come le bollette a fine mese. Lo vediamo ogni giorno nei progetti che arrivano a Meteora Web: app React meravigliose a livello UI ma fragili nel recupero dati. React Query (ora TanStack Query) risolve tutto questo con poche righe di codice. Non è una libreria in più — è il modo giusto di gestire lo stato del server.

Cos'è React Query e perché non dovresti più scrivere useEffect per il fetching?

React Query è una libreria per la gestione dello stato asincrono lato server. Sembra una definizione pomposa, ma nella pratica significa una cosa sola: non devi più occuparti di cache, loading, errori e sincronizzazione. Te ne occupa la libreria, e lo fa meglio di come potresti fare tu a mano.

Il problema con useEffect + fetch: ogni volta che un componente monta, parti da zero. Il dato che hai già caricato un minuto fa? Lo ricarichi. L'utente torna su una pagina? Altra richiesta. Più componenti sullo stesso endpoint? Duplicazione. Tutto questo si traduce in server stressato, esperienza utente lenta e codice gonfio.

La soluzione React Query: un unico punto di accesso per ogni chiave di query. Cache intelligente, refetch automatico in background, stale-while-revalidate, gestione errori globale, paginazione nativa. E — cosa fondamentale — separazione netta tra stato del server e stato del client. I dati che arrivano dal backend vivono in React Query; quelli locali (filtri, modali, form) restano in useState o useReducer. Non si mescolano più.

Esempio pratico — la differenza la vedi subito:

// Il vecchio modo: useEffect + useState
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => { setUser(data); setLoading(false); })
      .catch(err => { setError(err); setLoading(false); });
  }, [userId]);

  if (loading) return <Spinner />;
  if (error) return <Error message={error.message} />;
  return <div>{user.name}</div>;
}
// Con React Query
import { useQuery } from '@tanstack/react-query';

function UserProfile({ userId }) {
  const { data: user, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json())
  });

  if (isLoading) return <Spinner />;
  if (error) return <Error message={error.message} />;
  return <div>{user.name}</div>;
}

Quindici righe diventate otto. E in più hai cache automatica, refetch quando la finestra torna in primo piano, retry su errore, e una gestione dello stato sincrona con il server. Sembra magia, ma è solo ingegneria fatta bene.

Sponsored Protocol

Come iniziare subito: Installa la libreria con npm i @tanstack/react-query, avvolgi la tua app con QueryClientProvider e inizia a sostituire i tuoi useEffect con useQuery. Nel 90% dei casi funziona al primo colpo.

Come funziona la cache di React Query e perché ti fa risparmiare chiamate al server?

React Query non si limita a memorizzare i risultati: implementa un modello di cache a livelli che decide quando mostrare il dato vecchio e quando richiederne uno nuovo. I concetti chiave sono stale e gcTime (garbage collection time) — prima chiamato cacheTime.

  • staleTime: il tempo durante il quale il dato è considerato fresco. Se è fresco, React Query restituisce la cache senza fare alcuna richiesta di rete. Di default è 0, quindi ogni nuova sottoscrizione richiede un refetch. Impostando un valore (es. 30 secondi per dati semi-stabili) riduci le chiamate al server in modo drammatico.
  • gcTime: quanto tempo tenere i dati in memoria dopo che nessun componente li sta più osservando. Di default 5 minuti. Se l'utente naviga via e torna entro quel tempo, il dato è ancora disponibile — nessuna richiesta.

Esempio concreto da un progetto reale: un e-commerce che seguiamo ha un catalogo prodotti che cambia poche volte al giorno. Abbiamo impostato staleTime a 5 minuti. Il risultato? L'80% delle visite allo shop ha zero richieste al backend per i prodotti. Il server respira, l'utente vede le pagine istantaneamente perché il dato è già caldo.

// Configurazione personalizzata per un catalogo prodotti
const { data: products } = useQuery({
  queryKey: ['products'],
  queryFn: fetchProducts,
  staleTime: 5 * 60 * 1000, // 5 minuti
  gcTime: 10 * 60 * 1000     // 10 minuti in cache dopo l'ultimo uso
});

Cosa fare adesso: Analizza i tuoi dati. Quanto spesso cambiano? Ecco una regola pratica:

Sponsored Protocol

  • Dati quasi statici (categorie, liste paesi, impostazioni): staleTime = Infinity, gcTime = Infinity (o molto alto).
  • Dati semi-stabili (prodotti, articoli blog): staleTime = 30s-5min.
  • Dati in tempo reale (notifiche, messaggi): staleTime = 0 (default).

Come gestire mutazioni (POST, PUT, DELETE) con React Query senza perdere la cache?

La parte difficile arriva quando scrivi dati. Un form che invia un nuovo ordine, una modifica profilo, una cancellazione. Con useEffect+fetch l'approccio standard è: fai la chiamata, poi ricarichi tutto. Con React Query usi useMutation e aggiorni la cache in modo ottimistico o invalidando le query collegate.

Invalidazione automatica delle query

Dopo una mutazione, puoi dire a React Query: “i dati di questa chiave non sono più validi, ricaricali”. In questo modo il prossimo componente che usa quella query farà un refetch in background.

import { useMutation, useQueryClient } from '@tanstack/react-query';

function AddProductForm() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: (newProduct) => fetch('/api/products', {
      method: 'POST',
      body: JSON.stringify(newProduct)
    }),
    onSuccess: () => {
      // Invalida la lista prodotti per forzare un refetch
      queryClient.invalidateQueries({ queryKey: ['products'] });
    }
  });

  return <form onSubmit={/* chiama mutation.mutate */}>...</form>;
}

Aggiornamento ottimistico: quando la UX è critica (like, follow, commenti), puoi aggiornare la cache subito prima che la risposta del server arrivi. Se la richiesta fallisce, React Query ripristina lo stato precedente.

const mutation = useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    // Annulla tutte le query in corso per 'todos'
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    // Salva lo snapshot corrente per eventuale rollback
    const previousTodos = queryClient.getQueryData(['todos']);
    // Aggiorna ottimisticamente
    queryClient.setQueryData(['todos'], old => [...old, newTodo]);
    return { previousTodos };
  },
  onError: (err, newTodo, context) => {
    // Rollback allo snapshot
    queryClient.setQueryData(['todos'], context.previousTodos);
  },
  onSettled: () => {
    // Alla fine (successo o errore) invalida per sincronizzare con server
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  }
});

Attenzione: l'aggiornamento ottimistico va usato con cautela. Funziona benissimo per azioni reversibili e con API affidabili. Per ordini pagamenti, meglio invalidare e mostrare uno stato loading temporaneo.

Sponsored Protocol

Cosa fare adesso: Per ogni mutazione nel tuo progetto, chiediti: “Serve aggiornamento immediato o posso aspettare il refetch?”. Se la risposta è “immediato”, implementa l'aggiornamento ottimistico. Altrimenti, una semplice invalidazione basta e avanza.

Quali sono le strategie avanzate di prefetching e query di paginazione?

React Query non è solo per semplici GET. Gestisce paginazione, infinite scroll e prefetching nativo. Se hai una lista paginata (es. ordini cliente, prodotti filtrati), usare useQuery con una chiave che include il numero di pagina è immediato. Ma la vera potenza arriva con useInfiniteQuery per scroll infinito.

Paginazione classica

function ProductList({ page }) {
  const { data, isLoading, isPreviousData } = useQuery({
    queryKey: ['products', page],
    queryFn: () => fetch(`/api/products?page=${page}`).then(res => res.json()),
    keepPreviousData: true, // Mostra i dati della pagina precedente mentre carica la nuova
  });

  // ... render con pulsanti 'Precedente'/'Successivo'
}

Prefetching: puoi precaricare la pagina successiva mentre l'utente sta ancora leggendo la corrente, abbattendo il tempo di attesa a zero.

// Nel componente che visualizza la lista
const queryClient = useQueryClient();

// Quando l'utente passa sopra il pulsante 'Successivo'
const prefetchNextPage = () => {
  queryClient.prefetchQuery({
    queryKey: ['products', page + 1],
    queryFn: () => fetch(`/api/products?page=${page + 1}`).then(res => res.json())
  });
}

Infinite scroll con useInfiniteQuery

import { useInfiniteQuery } from '@tanstack/react-query';

function InfiniteComments() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage
  } = useInfiniteQuery({
    queryKey: ['comments'],
    queryFn: ({ pageParam = 1 }) => fetch(`/api/comments?page=${pageParam}`).then(res => res.json()),
    getNextPageParam: (lastPage, allPages) => {
      // Ritorna undefined se non ci sono più pagine
      return lastPage.nextPage ?? undefined;
    }
  });

  // Unisci tutte le pagine
  const comments = data?.pages.flatMap(page => page.items) || [];

  return (
    <div>
      {comments.map(comment => <CommentItem key={comment.id} {...comment} />)}
      {hasNextPage && (
        <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
          {isFetchingNextPage ? 'Caricamento...' : 'Carica altri'}
        </button>
      )}
    </div>
  );
}

Cosa fare adesso: Se il tuo progetto ha liste paginate, sostituisci il tuo sistema di paginazione manuale con useQuery + prefetch. Per l'infinite scroll, passa a useInfiniteQuery: ti gestisce automaticamente il salvataggio delle pagine e il caricamento della successiva.

Sponsored Protocol

Come integrare React Query con altri strumenti (React Router, state management) senza conflitti?

Una delle domande che ci fanno più spesso in Meteora Web: “React Query sostituisce Redux? Si può usare insieme a Zustand?”. La risposta è nessun conflitto, anzi: React Query si integra perfettamente con qualsiasi libreria di state management, perché gestisce un dominio diverso — i dati del server. Il segreto è tenere separati i due tipi di stato.

  • Server state (dati da API): solo React Query. Non duplicare mai in Redux o Zustand.
  • Client state (UI, form, filtri, modali): usa useState, useReducer, Zustand, Jotai, Pinia (per Vue, ma il principio è lo stesso).

Esempio pratico: un filtro in un campo input seleziona una categoria. La categoria è client state (useState), ma i prodotti filtrati sono server state (useQuery con queryKey che include la categoria come parametro).

function ShopPage() {
  const [category, setCategory] = useState('all'); // client state

  const { data: products } = useQuery({
    queryKey: ['products', category], // server state
    queryFn: () => fetch(`/api/products?category=${category}`).then(res => res.json())
  });

  return (
    <div>
      <select value={category} onChange={e => setCategory(e.target.value)}>
        <option value="all">Tutti</option>
        <option value="electronics">Elettronica</option>
      </select>
      {products?.map(p => <ProductCard key={p.id} {...p} />)}
    </div>
  );
}

Con React Router: se i parametri della query vengono dall'URL (es. /shop?category=electronics), usa useSearchParams di React Router per leggere e scrivere i parametri, e passali come parte del queryKey. React Query reagirà automaticamente al cambio di param.

Sponsored Protocol

Cosa fare adesso: Rivedi il tuo progetto. Se hai dati duplicati tra React Query e uno store, elimina la copia. Se hai componenti che leggono dal server via useEffect e scrivono in Redux, sposta tutto su useQuery. Vedrai il codice dimezzarsi.

Quali sono gli errori comuni con React Query e come evitarli?

Dopo aver visto decine di implementazioni, ecco gli errori che ripetono più spesso:

  1. QueryKey non abbastanza specifica: se usi ['user'] per un utente specifico, due pagine con ID diversi otterranno la stessa cache. Risultato: dati sbagliati. Rimedio: ogni parametro che influenza il risultato va nella queryKey.
  2. Dimenticare di invalidare dopo una mutazione: l'utente aggiunge un prodotto, ma la lista non si aggiorna. Rimedio: chiama queryClient.invalidateQueries nel onSuccess della mutation.
  3. StaleTime = Infinity senza gcTime adeguato: se i dati non diventano mai stale, ma la cache viene pulita dopo 5 minuti, l'utente vedrà un caricamento a sorpresa. Rimedio: se usi staleTime alto, alza anche gcTime.
  4. Non gestire il refetch in background con indicatori visivi: React Query mostra i dati vecchi mentre ne carica di nuovi, ma senza un isFetching l'utente non sa che sta aggiornando. Rimedio: usa isFetching per mostrare uno spinner di aggiornamento (non un full-page loading, ma un piccolo indicatore).
  5. Mischiare mutazioni con query senza separarle: creare troppi hook custom che combinano lettura e scrittura può portare a confusione. Rimedio: mantieni separati useQuery per lettura e useMutation per scrittura. Solo in casi molto semplici unisci.

Cosa fare adesso

  1. Installa React Query nel tuo progetto: npm install @tanstack/react-query
  2. Configura il provider nel punto più alto dell'albero dei componenti (App.tsx o Layout).
  3. Sostituisci un useEffect/fetch con useQuery. Inizia da un endpoint semplice — prodotti, utenti, articoli.
  4. Aggiungi una mutazione per un form e invalida la query collegata.
  5. Rivedi staleTime e gcTime in base alla frequenza di aggiornamento dei tuoi dati.
  6. Leggi la documentazione ufficiale su TanStack Query per approfondire paginazione e query infinite — il tempo investito ti ripaga in ore di debug risparmiate.
Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere informatico, fondatore di Meteora Web e Zenith OS. System administrator e progettista di piattaforme, app e CMS proprietari, con esperienza in sviluppo full-stack, marketing digitale ed ecosistema Google.
[ Read Full Dossier ]

> METEORA_WEB // WEB AGENCY

Costruiamo la presenza digitale che la tua azienda merita.

Siti web, social, pubblicità online, e-commerce e hosting performante: ingegnerizzati con metodo da ingegneri informatici a Sciacca, per tutta Italia.

> MW_JOURNAL

> READ_ALL()