useCallback è un hook React che restituisce una versione memoizzata di una funzione che non cambia tra i render finché le sue dipendenze non cambiano. A differenza di una dichiarazione di funzione normale all'interno di un componente (che crea una nuova funzione a ogni render), useCallback stabilizza il riferimento alla funzione, prevenendo re-render non necessari dei componenti figli ottimizzati con React.memo. Secondo React Documentation (2025), useCallback è utile solo in combinazione con React.memo o hook che dipendono da un riferimento stabile.
Punti chiave
useCallback è un hook aggiunto in React 16.8 che memoizza una funzione: restituisce lo stesso riferimento finché le dipendenze non cambiano. Senza useCallback, ogni dichiarazione di funzione all'interno di un componente crea un nuovo oggetto funzione a ogni render. Per i primitivi questo è impercettibile, ma quando si passano questi callback a componenti figli ottimizzati con React.memo, ogni nuovo riferimento causa un re-render del componente figlio.
Sintatticamente, useCallback è equivalente a useMemo per una funzione: useCallback(fn, deps) è una scorciatoia per useMemo(() => fn, deps). React memorizza la funzione memoizzata nell'archivio interno del nodo fiber e confronta le dipendenze a ogni render. Se le dipendenze non sono cambiate (Object.is per ogni elemento), viene restituita la funzione precedente.
Secondo React Documentation — useCallback (2025), non dovresti avvolgere ogni funzione in useCallback. L'hook ha il suo costo: chiamare l'hook, confrontare le dipendenze e allocare memoria per l'array di dipendenze. Se un componente è semplice e non ha alberi profondi con React.memo, useCallback rallenterà solo l'applicazione. L'ottimizzazione dovrebbe essere misurabile, non intuitiva.
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Riferimento stabile — stessa funzione fino al cambio delle deps
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return <Child onClick={handleClick} />;
}
La memoizzazione in useCallback si basa sulla memorizzazione nella cache del risultato della chiamata di funzione. React preserva la chiusura (closure) creata al momento del primo render e la restituisce nei render successivi finché le dipendenze rimangono invariate. All'interno del nodo fiber, ogni chiamata useCallback crea un nodo nella lista collegata degli hook, dove vengono memorizzate le dipendenze precedenti e il valore memoizzato.
Il confronto delle dipendenze viene eseguito rigorosamente tramite Object.is — un confronto superficiale senza controllo approfondito di oggetti o array. Se una dipendenza è un oggetto o un array, un nuovo riferimento a ogni render sarà considerato una modifica. Pertanto, l'array di dipendenze dovrebbe contenere valori primitivi o riferimenti stabili (ad esempio, da useRef o useMemo).
Secondo React Core Team — Optimization Guide (2024), il costo della memoizzazione include tre componenti: allocare l'array di dipendenze a ogni render, iterare e confrontare gli elementi tramite Object.is e il potenziale overhead di garbage collection durante la ricreazione. Per un componente con centinaia di wrapper useCallback, questo può diventare evidente — quindi la selettività nell'uso dell'hook è fondamentale.
| Scenario | Senza useCallback | Con useCallback |
|---|---|---|
| Creazione funzione | Nuova a ogni render | Uguale con deps stabili |
| Passato a React.memo | Figlio si re-renderizza | Figlio non si re-renderizza |
| Nell'array di useEffect | Effetto si riavvia | Effetto stabile |
| Overhead | Minimo | Confronto dipendenze + memoria |
Esiste un mito diffuso che useCallback migliori automaticamente le prestazioni. In realtà, isolatamente (senza React.memo), useCallback rallenta anche leggermente l'applicazione a causa del costo del confronto delle dipendenze. L'hook porta un reale beneficio solo in tre scenari: prevenire re-render dei componenti React.memo, stabilizzare i callback in useEffect e passare callback a hook personalizzati che dipendono dall'uguaglianza di riferimento.
La regola è semplice: finché non rilevi un problema di prestazioni con React DevTools Profiler — non usare useCallback. Il team di React ha ripetutamente sottolineato che l'ottimizzazione prematura è la radice di tutti i mali. Prima scrivi codice pulito senza memoizzazione, misura, trova il collo di bottiglia nel profiler e solo allora aggiungi useCallback dove è davvero necessario.
// Ottimizzazione misurabile: Child è avvolto in React.memo
const Child = React.memo(({ onClick }) => {
console.log('Child ri-renderizzato');
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const handleClick = useCallback(() => {
console.log('cliccato');
}, []);
return <Child onClick={handleClick} />;
}
Secondo Dan Abramov — Before You memo() (2024), oltre il 90% dei casi d'uso di useCallback nei progetti open-source sono ridondanti. Gli sviluppatori avvolgono ogni funzione "per sicurezza" senza misurarne l'effetto. L'alternativa: se il componente figlio è pesante e il suo re-render è costoso — React.memo + useCallback è giustificato. Se il componente figlio è leggero — il re-render è più economico del confronto delle dipendenze.
Il primo scenario è React.memo. Se un componente figlio è avvolto in React.memo e riceve una funzione callback come prop, senza useCallback il componente figlio si re-renderizza a ogni render del genitore, anche se i suoi dati non sono cambiati. useCallback stabilizza il riferimento, permettendo a React.memo di saltare correttamente il re-render.
Il secondo scenario è useEffect con un callback nelle dipendenze. Se una funzione viene passata all'array di dipendenze di useEffect, ogni nuovo riferimento riavvierà l'effetto. useCallback garantisce che il riferimento sia stabile e che l'effetto venga eseguito solo quando i dati effettivi cambiano, non a ogni render. Questo è particolarmente importante per sottoscrizioni e richieste.
// useCallback per dipendenza useEffect stabile
const fetchData = useCallback(async (id) => {
const res = await fetch(`/api/${id}`);
setData(res.data);
}, []); // riferimento stabile, mai ricreato
useEffect(() => {
fetchData(props.id);
}, [props.id, fetchData]); // effetto eseguito solo quando props.id cambia
La differenza principale tra useCallback e useMemo è ciò che ciascuno memoizza. useCallback memoizza una funzione: useCallback(fn, deps) restituisce fn (la stessa o la versione precedente). useMemo memoizza il risultato di una chiamata di funzione: useMemo(() => computeExpensive(a, b), [a, b]) restituisce il valore calcolato, non una funzione.
Tecnicamente, useCallback è zucchero sintattico su useMemo: useCallback(fn, deps) è equivalente a useMemo(() => fn, deps). Questa sintassi esiste solo per la leggibilità — affinché lo sviluppatore veda chiaramente che viene memoizzata una funzione, non un valore. Non c'è differenza di prestazioni tra useCallback e useMemo con una funzione — generano codice identico.
| Hook | Memoizza | Sintassi | Utilizzo |
|---|---|---|---|
| useCallback | Funzione (riferimento) | useCallback(fn, deps) | Callback per componenti figli |
| useMemo | Risultato di calcolo | useMemo(() => value, deps) | Calcoli costosi, memoizzazione oggetti |
// Questi sono equivalenti:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);
L'errore più comune è avvolgere inutilmente tutte le funzioni in useCallback senza React.memo sui componenti figli. Se un componente figlio non è avvolto in React.memo, si re-renderizza comunque a ogni render del genitore, indipendentemente dal fatto che il riferimento del callback cambi o meno. useCallback senza React.memo è costo senza beneficio.
// ❌ Inutile: nessun React.memo sul figlio
const handle = useCallback(() => doStuff(), []);
<Child onClick={handle} />; // Child si re-renderizza comunque senza React.memo
// ❌ Stale closure: dipendenza mancante
const handle = useCallback(() => {
console.log(count); // count è sempre 0 — stale closure!
}, []);
// ✅ Corretto: includere le dipendenze
const handle = useCallback(() => {
console.log(count);
}, [count]);
Il problema della stale closure in useCallback si risolve includendo tutte le variabili utilizzate nell'array di dipendenze. eslint-plugin-react-hooks con exhaustive-deps verifica automaticamente che tutte le variabili dal corpo del callback siano presenti nell'array. Se il callback usa setState, che non cambia tra i render, può essere incluso tranquillamente in deps — React garantisce la stabilità di setState.
Domande frequenti
No. useCallback ha senso solo in tre casi: il componente figlio è avvolto in React.memo, la funzione è usata nell'array di dipendenze di useEffect, o la funzione è passata a un hook personalizzato che dipende dall'uguaglianza di riferimento. Negli altri casi, useCallback aggiunge overhead senza beneficio. Il team React consiglia di scrivere prima senza ottimizzazioni e aggiungerle in base ai risultati del profiling.
Per componenti semplici — una nuova funzione a ogni render è leggermente più veloce, poiché useCallback spende risorse per confrontare le dipendenze e allocare l'array. Per componenti con alberi React.memo profondi, useCallback vince prevenendo il re-render di migliaia di elementi figli. Misura e confronta invece di indovinare — usa React DevTools Profiler per una valutazione oggettiva.
Sì, useCallback funziona con funzioni async esattamente come con quelle sincrone. L'hook memoizza la funzione stessa e il risultato (una Promise) viene restituito ogni volta che viene chiamata. Una funzione async dentro useCallback è un pattern comune per callback stabili di caricamento dati usati in useEffect: const fetchData = useCallback(async (id) => {...}, []).
Usa React DevTools Profiler — mostra quali componenti si re-renderizzano e perché. Per verifica programmatica, aggiungi console.log o usa useWhyDidYouUpdate — una libreria che registra la causa del re-render. Le cause principali: una prop è cambiata (incluso il riferimento del callback), lo stato è cambiato o il contesto è cambiato. Se useCallback non aiuta — verifica che tutte le dipendenze siano correttamente specificate.
Anche senza React.memo, useCallback può essere utile in combinazione con useMemo per i valori del contesto. Se stai passando un oggetto con funzioni a Context.Provider, avvolgi la creazione dell'oggetto in useMemo e ogni funzione in useCallback. Questo impedisce il re-render di tutti i consumatori del contesto quando una delle funzioni cambia. Ma per passare callback direttamente nelle props senza React.memo, non c'è beneficio da useCallback.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche