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

> Pagina: https://ui.eneawork.it/blog/react-data-fetching-senza-librerie
> Markdown: https://ui.eneawork.it/blog/react-data-fetching-senza-librerie.md

*2026-11-01*

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.

I numeri arrivano da npm, misurati il 1 novembre 2026: @tanstack/react-query 5.105.1 dichiara 1.796.835 byte non compressi, swr 2.5.1 ne dichiara 320.306, il comando usato è npm view @tanstack/react-query dist.unpackedSize. Entrambi hanno licenza MIT. Il fetch del browser pesa zero byte: le librerie aggiungono codice di cache e orchestrazione, non la capacità di fare una richiesta.

## 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

## 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à.

## FAQ

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