React
Fetching dati senza librerie
Effetto con AbortController: richiesta, errore e annullamento in trenta righe. Cache, retry e rivalidazione sono i soli motivi per aggiungere un pacchetto.
In sintesi
- Un effetto con `AbortController` copre richiesta, annullamento e stato di errore, senza dipendenze.
- `fetch` non rifiuta su 404 o 500: il controllo su `res.ok` è obbligatorio.
- Cache condivisa, retry e rivalidazione sono i tre pezzi che una libreria aggiunge davvero.
Risposta breve: per una richiesta per componente bastano fetch e un effetto con AbortController. Stato, errore e annullamento stanno in trenta righe, senza pacchetti aggiunti al bundle.
Il caso normale
import { useEffect, useState } from "react";
type State =
| { status: "loading" }
| { status: "error"; error: Error }
| { status: "done"; data: T };
export function useFetch(url: string): State {
const [state, setState] = useState>({ status: "loading" });
useEffect(() => {
const controller = new AbortController();
setState({ status: "loading" });
fetch(url, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error("HTTP " + res.status);
return res.json() as Promise;
})
.then((data) => setState({ status: "done", data }))
.catch((error: unknown) => {
if (error instanceof DOMException && error.name === "AbortError") return;
setState({ status: "error", error: error as Error });
});
return () => controller.abort();
}, [url]);
return state;
}
Il controllo su res.ok serve perché fetch non rifiuta su un 404 o un 500: la promise si risolve e la decisione resta a te. Il signal annulla la richiesta quando il componente esce di scena, quindi nessun aggiornamento di stato arriva su un componente smontato.
Cosa resta da scrivere a mano
- Cache: due componenti che chiedono lo stesso URL fanno due richieste, a meno che non condividano una struttura esterna all'effetto.
- Retry: un 503 non si ritenta da solo, serve un ciclo con attesa crescente e un tetto di tentativi.
- Rivalidazione: dopo una modifica i dati vecchi restano finché qualcuno non invalida la copia che hai salvato.
Il confine con le librerie
Quando tutte e tre le voci servono davvero, una libreria costa meno del codice scritto a mano. TanStack Query 5.105.1 è MIT e porta cache condivisa, retry e stato di caricamento; SWR 2.5.1 è MIT, pesa meno ed è pensato per la rivalidazione al focus. La differenza pratica è la superficie dell'API, non la qualità.
Il progetto, da zero
npm create vite@latest fetch-demo -- --template react-ts
cd fetch-demo
npm install
npm run dev
Record vivi nel catalogo
Per l'interfaccia intorno al caricamento il catalogo ha record pronti: la libreria shadcn, i blocchi dashboard con tabelle e stati vuoti, e il componente spinner. Ogni record dichiara la licenza sulla propria pagina.
La limitazione
L'hook qui sopra non deduplica e non condivide niente: in StrictMode gli effect partono due volte, quindi la prima richiesta viene annullata e rifatta, e in sviluppo vedi due chiamate di rete. È il comportamento descritto in perché gli effect partono due volte, non un errore del tuo codice.
Dove continuare
Il modello generale è in che cos'è React; quando i dati arrivano dal server il confronto è in streaming e Suspense nell'App Router. Se stai scegliendo una libreria, la classifica delle librerie React 2026 parte dai numeri del catalogo; per il costo lato utente leggi INP e reattività.
Domande frequenti
Serve una libreria per fare fetch in React?
No. Per una richiesta per componente bastano fetch e un effetto con AbortController; una libreria serve quando vuoi cache condivisa, retry e rivalidazione già scritti.
Perché la richiesta parte due volte in sviluppo?
Perché StrictMode monta, smonta e rimonta il componente: il primo effect viene annullato e il secondo esegue la richiesta. In produzione l'effetto gira una volta sola.
Come annullo una richiesta quando cambio pagina?
Passi un AbortController tramite l'opzione signal e chiami abort nella funzione di pulizia dell'effetto. Il catch deve ignorare l'errore con nome AbortError.
Posso usare async direttamente dentro useEffect?
No, la funzione passata a useEffect non può essere async, perché restituirebbe una promise al posto della pulizia. Dichiari una funzione async dentro l'effetto e la chiami.
fetch lancia un errore su 404?
No. La promise si risolve anche su 404 e 500: devi controllare res.ok e lanciare tu un errore, oppure gestire lo stato di errore nel then.