useEffect
Cos’è useEffect
useEffect è l’hook di React per eseguire effetti collaterali (side effects) nei componenti funzionali. Un side effect è qualsiasi operazione che interagisce con il mondo esterno al componente: chiamate API, manipolazione del DOM, timer, sottoscrizioni a eventi.
import { useEffect, useState } from "react";
function Titolo({ pagina }) {
useEffect(() => {
document.title = `Pagina: ${pagina}`;
}, [pagina]);
return <h1>{pagina}</h1>;
}
Sintassi di useEffect
useEffect(
() => {
// Codice dell'effetto (eseguito dopo il render)
return () => {
// Funzione di cleanup (opzionale)
};
},
[dipendenza1, dipendenza2] // Array di dipendenze (opzionale)
);
L’effetto viene eseguito dopo che React ha aggiornato il DOM, non durante il rendering.
Array di Dipendenze
L’array di dipendenze controlla quando l’effetto viene ri-eseguito.
Nessun Array: Esegui ad Ogni Render
useEffect(() => {
console.log("Eseguito dopo OGNI render");
});
Questo pattern è raramente utile e può causare problemi di performance. Usalo con estrema cautela.
Array Vuoto: Solo al Mount
useEffect(() => {
console.log("Eseguito solo al primo render (mount)");
return () => {
console.log("Eseguito quando il componente si smonta (unmount)");
};
}, []);
Con Dipendenze: Quando Cambiano
useEffect(() => {
console.log(`L'effetto si esegue perché userId è cambiato: ${userId}`);
// Fetch dati dell'utente
fetchUtente(userId);
}, [userId]); // Si ri-esegue SOLO quando userId cambia
Come React Confronta le Dipendenze
React usa Object.is() per confrontare i valori delle dipendenze tra un render e l’altro. Per i valori primitivi (numeri, stringhe, booleani) il confronto è per valore. Per oggetti e array è per riferimento.
// Primitivi: confronto per valore
const [count, setCount] = useState(0);
useEffect(() => { ... }, [count]); // Si esegue quando count cambia valore
// Oggetti: confronto per riferimento
const filtri = { categoria: "libri" }; // Nuovo oggetto ad ogni render!
useEffect(() => { ... }, [filtri]); // Si esegue ad OGNI render (bug)
// Soluzione: useMemo o stato
const [filtri, setFiltri] = useState({ categoria: "libri" });
useEffect(() => { ... }, [filtri]); // Si esegue solo quando setFiltri viene chiamato
Funzione di Cleanup
La funzione di cleanup viene eseguita prima che l’effetto venga ri-eseguito e quando il componente si smonta. È essenziale per evitare memory leak.
function Timer() {
const [secondi, setSecondi] = useState(0);
useEffect(() => {
const intervallo = setInterval(() => {
setSecondi((prev) => prev + 1);
}, 1000);
// Cleanup: rimuovi l'intervallo
return () => clearInterval(intervallo);
}, []);
return <p>Tempo: {secondi}s</p>;
}
Ordine di Esecuzione
useEffect(() => {
console.log("1. Effetto eseguito");
return () => {
console.log("2. Cleanup eseguito");
};
}, [dipendenza]);
// Al mount: "1. Effetto eseguito"
// Quando dipendenza cambia: "2. Cleanup eseguito" → "1. Effetto eseguito"
// Al unmount: "2. Cleanup eseguito"
Fetch di Dati
Uno degli usi più comuni di useEffect è il caricamento di dati da un’API.
function ListaUtenti() {
const [utenti, setUtenti] = useState([]);
const [caricamento, setCaricamento] = useState(true);
const [errore, setErrore] = useState(null);
useEffect(() => {
// Flag per evitare aggiornamenti su componente smontato
let cancellato = false;
async function caricaUtenti() {
try {
setCaricamento(true);
const risposta = await fetch("https://api.example.com/utenti");
if (!risposta.ok) {
throw new Error(`Errore HTTP: ${risposta.status}`);
}
const dati = await risposta.json();
if (!cancellato) {
setUtenti(dati);
setErrore(null);
}
} catch (err) {
if (!cancellato) {
setErrore(err.message);
}
} finally {
if (!cancellato) {
setCaricamento(false);
}
}
}
caricaUtenti();
return () => {
cancellato = true; // Cleanup: previeni aggiornamenti dopo unmount
};
}, []);
if (caricamento) return <p>Caricamento...</p>;
if (errore) return <p>Errore: {errore}</p>;
return (
<ul>
{utenti.map((u) => (
<li key={u.id}>{u.nome}</li>
))}
</ul>
);
}
Fetch con AbortController
Un approccio più robusto usa AbortController per cancellare richieste in corso.
function DettaglioUtente({ userId }) {
const [utente, setUtente] = useState(null);
const [caricamento, setCaricamento] = useState(true);
useEffect(() => {
const controller = new AbortController();
async function caricaUtente() {
try {
setCaricamento(true);
const risposta = await fetch(
`https://api.example.com/utenti/${userId}`,
{ signal: controller.signal }
);
const dati = await risposta.json();
setUtente(dati);
} catch (err) {
if (err.name !== "AbortError") {
console.error("Errore nel fetch:", err);
}
} finally {
setCaricamento(false);
}
}
caricaUtente();
return () => controller.abort(); // Cancella la richiesta se userId cambia
}, [userId]);
if (caricamento) return <Spinner />;
if (!utente) return <p>Utente non trovato</p>;
return <h1>{utente.nome}</h1>;
}
Event Listener
function RilevatoreResize() {
const [dimensione, setDimensione] = useState({
larghezza: window.innerWidth,
altezza: window.innerHeight,
});
useEffect(() => {
const handleResize = () => {
setDimensione({
larghezza: window.innerWidth,
altezza: window.innerHeight,
});
};
window.addEventListener("resize", handleResize);
// Cleanup: rimuovi l'event listener
return () => window.removeEventListener("resize", handleResize);
}, []);
return (
<p>
Finestra: {dimensione.larghezza}x{dimensione.altezza}
</p>
);
}
Listener su Keyboard
function ScorciatoieTastiera() {
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === "s") {
e.preventDefault();
console.log("Salvataggio...");
}
if (e.key === "Escape") {
console.log("Chiusura modale...");
}
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, []);
return <p>Premi Ctrl+S per salvare, Esc per chiudere</p>;
}
Sincronizzazione con Sistemi Esterni
useEffect è pensato per sincronizzare il componente con sistemi esterni.
function ChatRoom({ roomId }) {
useEffect(() => {
// Connessione al sistema esterno (chat)
const connessione = creaConnessioneChat(roomId);
connessione.connetti();
// Cleanup: disconnessione
return () => connessione.disconnetti();
}, [roomId]); // Ri-connetti quando cambia stanza
return <h1>Stanza: {roomId}</h1>;
}
Quando NON Usare useEffect
Molti sviluppatori abusano di useEffect. Ecco i casi in cui non dovresti usarlo.
Trasformare Dati per il Rendering
// SBAGLIATO: useEffect per derivare dati
function ListaFiltrata({ items, filtro }) {
const [filtrati, setFiltrati] = useState([]);
useEffect(() => {
setFiltrati(items.filter((i) => i.categoria === filtro));
}, [items, filtro]);
return <Lista items={filtrati} />;
}
// CORRETTO: calcola durante il render
function ListaFiltrata({ items, filtro }) {
const filtrati = items.filter((i) => i.categoria === filtro);
return <Lista items={filtrati} />;
}
Gestire Eventi Utente
// SBAGLIATO: useEffect per reagire a un'azione utente
function Carrello() {
const [prodotto, setProdotto] = useState(null);
useEffect(() => {
if (prodotto) {
aggiungiAlCarrello(prodotto);
}
}, [prodotto]);
return <button onClick={() => setProdotto(item)}>Aggiungi</button>;
}
// CORRETTO: gestisci l'azione nell'handler dell'evento
function Carrello() {
const handleAggiungi = (item) => {
aggiungiAlCarrello(item);
};
return <button onClick={() => handleAggiungi(item)}>Aggiungi</button>;
}
Reset dello Stato al Cambio di Props
// SBAGLIATO
function ProfiloEditor({ userId }) {
const [nome, setNome] = useState("");
useEffect(() => {
setNome(""); // Reset quando cambia utente
}, [userId]);
}
// CORRETTO: usa la prop key per forzare il remount
function App({ userId }) {
return <ProfiloEditor key={userId} userId={userId} />;
}
Effetti Multipli
Separa effetti non correlati in chiamate useEffect distinte.
function Dashboard({ userId }) {
const [utente, setUtente] = useState(null);
const [notifiche, setNotifiche] = useState([]);
// Effetto 1: Carica dati utente
useEffect(() => {
fetchUtente(userId).then(setUtente);
}, [userId]);
// Effetto 2: Sottoscrivi notifiche (indipendente)
useEffect(() => {
const sub = sottoscriviNotifiche(userId, setNotifiche);
return () => sub.cancella();
}, [userId]);
// Effetto 3: Aggiorna titolo pagina (indipendente)
useEffect(() => {
document.title = utente ? `Dashboard - ${utente.nome}` : "Dashboard";
}, [utente]);
return <div>{/* ... */}</div>;
}
Riepilogo
useEffect serve per sincronizzare il componente con sistemi esterni: API, DOM, timer, WebSocket. Ricorda sempre di gestire il cleanup, usa l’array di dipendenze correttamente, e non usare useEffect per logica che può essere gestita durante il render o negli event handler. Per il data fetching in applicazioni reali, considera librerie come TanStack Query che gestiscono caching, retry e deduplicazione automaticamente.