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

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.