useEffect esegue un pezzo di codice dopo che React ha renderizzato il tuo componente e aggiornato lo schermo. Lo usi per mantenere un componente sincronizzato con qualcosa al di fuori di React, come un timer, una connessione di rete o un event listener, e controlli quando viene eseguito di nuovo con un array delle dipendenze.
Apri la console sotto l'anteprima: l'effetto registra un log una volta quando il componente compare, poi di nuovo dopo ogni clic. Cambia [count] in [] e clicca di nuovo: dopo il primo log l'effetto resta in silenzio.
La sintassi
useEffect(() => {
// setup: runs after the render is on the screen
return () => {
// cleanup: runs before the next setup, and on unmount
};
}, [dependencies]);
- Il primo argomento è la funzione di setup. Può restituire una funzione di cleanup, oppure nulla.
- Il secondo argomento è l'array delle dipendenze: ogni valore del componente che il setup legge (props, stato e qualsiasi cosa calcolata a partire da essi).
- Chiama
useEffectal livello più alto del componente, come ogni hook.
Gli effetti vengono eseguiti dopo il rendering
Il rendering è React che chiama la funzione del tuo componente per sapere cosa deve mostrare lo schermo. Gli effetti aspettano che quel lavoro sia finito e che il DOM sia aggiornato, quindi dentro un effetto la pagina mostra già i nuovi valori.
Ogni clic registra prima il rendering e poi l'effetto, e l'effetto legge il testo del pulsante che React ha appena scritto. È per questo che leggere il DOM, avviare un timer o connettersi a un server va fatto in un effetto e non nel corpo del componente: il corpo viene eseguito durante il rendering, prima che ci sia qualcosa sullo schermo, e React potrebbe chiamarlo più spesso di quanto ti aspetti.
Di solito gli effetti vengono eseguiti dopo che il browser ha disegnato la pagina. Quando un effetto deve misurare il DOM prima che venga disegnato, usa invece useLayoutEffect, che viene sempre eseguito prima del disegno.
L'array delle dipendenze: tre forme
L'array delle dipendenze decide quando l'effetto viene eseguito di nuovo. Ci sono tre forme, e questo blocco le usa tutte così puoi osservare la differenza.
Al caricamento tutti e tre gli effetti registrano un log. Clicca a + 1 e ne vengono eseguiti due: quello senza array e quello che dipende da a. Clicca b + 1 e viene eseguito solo quello senza array, perché in [] e in [a] non è cambiato nulla.
| Array delle dipendenze | Quando viene eseguito l'effetto |
|---|---|
| nessuno | Dopo ogni rendering |
[] | Una volta, dopo il primo rendering (al mount) |
[a, b] | Dopo il primo rendering, poi ogni volta che a o b sono cambiati |
React confronta ogni dipendenza con il suo valore precedente usando Object.is. Numeri, stringhe e booleani si confrontano per valore. Oggetti, array e funzioni si confrontano per identità, il che conta molto (vedi sotto).
Non togliere un valore dall'array per far eseguire un effetto meno spesso. L'effetto continuerebbe a usare il valore di un rendering vecchio. La regola di lint react-hooks/exhaustive-deps lo intercetta; fa parte della configurazione ESLint inclusa nel template React di Vite e nella configurazione ESLint di Next.js.
Cleanup
Molti effetti avviano qualcosa che deve essere fermato: una connessione, una sottoscrizione, un timer. Restituisci una funzione dall'effetto e React la chiama in due momenti: subito prima che l'effetto venga eseguito di nuovo con nuove dipendenze, e quando il componente lascia lo schermo (unmount).
Scegli un'altra stanza: la console mostra disconnect from general e poi connect to music. La vecchia connessione si chiude sempre prima che si apra quella nuova. Clicca Hide e la funzione di cleanup viene eseguita un'ultima volta, perché ChatRoom è stato smontato.
La funzione connect qui fa le veci di un vero client di chat o WebSocket. La forma è la stessa per tutti: lo crei nel setup, lo chiudi nella funzione di cleanup.
Cancellare un timer
Un timer avviato in un effetto continua a girare dopo che il componente è sparito, a meno che la funzione di cleanup non lo fermi.
Elimina la riga clearInterval(id);, poi nascondi il timer: tick continua ad arrivare nella console da un componente che non esiste più. Ogni Show ne avvia un altro.
Nota setSeconds((s) => s + 1). L'effetto viene eseguito una volta, quindi un semplice setSeconds(seconds + 1) leggerebbe per sempre seconds del primo rendering e si fermerebbe a 1. La funzione di aggiornamento riceve sempre il valore più recente.
Rimuovere un event listener
Gli event listener su window o document seguono lo stesso schema. Passa a removeEventListener la stessa funzione che hai passato ad addEventListener:
useEffect(() => {
function handleKeyDown(e) {
if (e.key === 'Escape') onClose();
}
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [onClose]);
Il ciclo infinito
Un effetto che imposta un valore di stato da cui dipende anche avvia un ciclo: lo stato cambia, React esegue il rendering, la dipendenza è cambiata, l'effetto viene eseguito e imposta di nuovo lo stato.
// Never stops: every render runs the effect, every effect causes a render
useEffect(() => {
setCount(count + 1);
});
// Same loop: count changes, so the effect runs again
useEffect(() => {
setCount(count + 1);
}, [count]);
Il componente si renderizza senza fine e la scheda resta occupata. React non ferma questo ciclo: in sviluppo registra nella console un errore "Maximum update depth exceeded", e in produzione non registra nulla. Questo blocco esegue la seconda versione con una condizione di arresto, così puoi osservare il ciclo senza bloccare la pagina:
La console registra da render 0 a render 5: sei rendering per un solo mount, ognuno avviato dall'effetto. Le soluzioni dipendono da ciò che intendevi:
- Volevi eseguirlo una volta. Usa
[]e la forma con funzione di aggiornamento,setCount((c) => c + 1), cosìcountnon è una dipendenza. - Stai calcolando un valore da altro stato. Non memorizzarlo affatto; calcolalo durante il rendering, come mostrato nell'ultima sezione di questa pagina.
- Stai reagendo a un'azione dell'utente. Metti il codice nel gestore di eventi.
Oggetti e funzioni come dipendenze
Un oggetto, un array o una funzione creati nel corpo del componente sono un valore nuovo a ogni rendering, quindi un effetto che dipende da essi viene eseguito dopo ogni rendering, anche quando al loro interno non è cambiato nulla.
Cambiare i colori non ha nulla a che fare con la ricerca, eppure ogni cambio registra di nuovo search for react. Sposta la riga options dentro l'effetto e cambia l'array in [query]: i cambi tornano silenziosi e solo scrivere nell'input esegue la ricerca.
Questa è la soluzione generale. Crea oggetti e funzioni dentro l'effetto che li usa, e dipendi dai valori primitivi a partire dai quali sono costruiti. Quando una funzione deve vivere fuori dall'effetto, useCallback mantiene stabile la sua identità tra un rendering e l'altro.
Perché gli effetti vengono eseguiti due volte in sviluppo
Con <StrictMode> attivo (una nuova app Vite racchiude la radice al suo interno, e l'App Router di Next.js lo attiva per default), React esegue un test in sviluppo: monta ogni componente, esegue i suoi effetti, esegue le loro funzioni di cleanup ed esegue di nuovo gli effetti. Per la stanza della chat qui sopra, la console di sviluppo mostra:
connect to general
disconnect from general
connect to general
Succede una volta, al mount, e solo in sviluppo. Esiste per far emergere gli effetti a cui manca una funzione di cleanup: se due esecuzioni di fila rompono qualcosa, l'utente lo romperebbe a sua volta uscendo e tornando. La soluzione non è mai bloccare la seconda esecuzione con un ref; è scrivere la funzione di cleanup. L'anteprima di questa pagina gira come una build di produzione, quindi qui non vedrai la doppia esecuzione.
Forse non ti serve un effetto
Gli effetti servono a sincronizzarsi con sistemi al di fuori di React. Due usi comuni funzionano meglio senza.
Valori calcolati da props o stato. Calcolali durante il rendering. Memorizzarli nello stato e sincronizzarli con un effetto costa un rendering in più, e il primo di quei rendering mette nel DOM un valore vecchio:
Ogni clic renderizza WithEffect due volte, e il suo primo rendering mostra ancora il vecchio nome. Computed si renderizza una volta con il valore giusto.
Codice che risponde a un'azione dell'utente. Inviare un form, mostrare una notifica dopo un acquisto o inviare un evento di analytics per un clic va nel gestore di eventi. Lì sai esattamente cosa è successo, mentre un effetto sa solo che un certo valore è cambiato.
useEffectEvent
A volte un effetto ha bisogno del valore più recente di una prop senza essere eseguito di nuovo quando quella prop cambia. Nella stanza della chat, una notifica dovrebbe usare il theme attuale, ma cambiare il tema non dovrebbe ricollegarsi. React 19.2 aggiunge useEffectEvent per questo:
import { useEffect, useEffectEvent } from 'react';
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme); // always the latest theme
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', () => onConnected());
connection.connect();
return () => connection.disconnect();
}, [roomId]); // theme is not a dependency
}
Una funzione ottenuta da useEffectEvent è solo da chiamare dentro gli effetti. Non passarla ad altri componenti e non elencarla in un array delle dipendenze.
Domande frequenti
Quando viene eseguito useEffect?
Dopo che React ha renderizzato il componente e aggiornato il DOM, di solito dopo che il browser ha disegnato la pagina. Senza array delle dipendenze viene eseguito dopo ogni rendering, con [] solo dopo il primo, e con [a, b] dopo il primo rendering e dopo ogni rendering in cui a o b sono cambiati.
Cos'è la funzione di cleanup di useEffect?
La funzione che restituisci dall'effetto. React la chiama prima di eseguire di nuovo l'effetto con nuove dipendenze, e un'ultima volta quando il componente viene rimosso dallo schermo. Usala per cancellare timer, rimuovere event listener e chiudere sottoscrizioni.
Perché il mio useEffect viene eseguito due volte?
In sviluppo, <StrictMode> monta ogni componente, esegue i suoi effetti, li pulisce e li esegue di nuovo, per mostrarti gli effetti a cui manca una funzione di cleanup. Succede una volta al mount e solo in sviluppo. Le build di produzione eseguono l'effetto una volta.
Perché il mio useEffect provoca un ciclo infinito?
L'effetto imposta un valore di stato che compare anche nel suo array delle dipendenze (oppure non ha alcun array). Impostare lo stato provoca un nuovo render, la dipendenza è cambiata, quindi l'effetto viene eseguito di nuovo. Usa una funzione di aggiornamento e togli la dipendenza, aggiungi una condizione, oppure calcola il valore durante il rendering.
La callback di useEffect può essere async?
No. La callback deve restituire nulla o una funzione di cleanup, e una funzione async restituisce una promise. Definisci una funzione async dentro l'effetto e chiamala, come mostrato nella pagina sul recupero dei dati.