Nota bene

Questa pagina è stata tradotta automaticamente e supervisionata da un maintainer. Un’ulteriore revisione da parte della community sarebbe comunque utile. Migliora questa traduzione.

useEffect è un Hook React che ti permette di sincronizzare un componente con un sistema esterno.

useEffect(setup, dependencies?)

Reference

useEffect(setup, dependencies?)

Chiama useEffect al top level del tuo componente per dichiarare un Effetto:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}

Vedi altri esempi sotto.

Parameters

  • setup: La funzione con la logica del tuo Effetto. La funzione di setup può anche restituire opzionalmente una funzione di cleanup. Quando il tuo componente esegue la fase di commit, React eseguirà la funzione di setup. Dopo ogni fase di commit con dipendenze cambiate, React eseguirà prima la funzione di cleanup (se l’hai fornita) con i valori precedenti, e poi eseguirà la funzione di setup con i nuovi valori. Dopo che il componente viene rimosso dal DOM, React eseguirà la funzione di cleanup.

  • optional dependencies: L’elenco di tutti i valori reattivi referenziati all’interno del codice di setup. I valori reattivi includono props, state e tutte le variabili e funzioni dichiarate direttamente nel corpo del componente. Se il tuo linter è configurato per React, verificherà che ogni valore reattivo sia specificato correttamente come dipendenza. L’elenco delle dipendenze deve avere un numero costante di elementi ed essere scritto inline come [dep1, dep2, dep3]. React confronterà ogni dipendenza con il suo valore precedente usando il confronto Object.is. Se ometti questo argomento, il tuo Effetto verrà rieseguito dopo ogni fase di commit del componente. Vedi la differenza tra passare un array di dipendenze, un array vuoto e nessuna dipendenza.

Returns

useEffect restituisce undefined.

Caveats

  • useEffect è un Hook, quindi puoi chiamarlo solo al top level del tuo componente o dei tuoi Hook. Non puoi chiamarlo all’interno di loop o condizioni. Se ne hai bisogno, estrai un nuovo componente e sposta lo state al suo interno.

  • Se non stai cercando di sincronizzarti con un sistema esterno, probabilmente non ti serve un Effetto.

  • Quando Strict Mode è attivo, React eseguirà un ciclo setup+cleanup extra solo in development prima del primo setup reale. È un test di stress che verifica che la logica di cleanup “specchi” la logica di setup e che fermi o annulli ciò che fa il setup. Se questo causa un problema, implementa la funzione di cleanup.

  • Se alcune delle tue dipendenze sono oggetti o funzioni definite all’interno del componente, c’è il rischio che facciano rieseguire l’Effetto più spesso del necessario. Per risolvere, rimuovi le dipendenze oggetto e funzione non necessarie. Puoi anche estrarre gli aggiornamenti di state e la logica non reattiva fuori dall’Effetto.

  • Se il tuo Effetto non è stato causato da un’interazione (come un click), React in genere lascerà che il browser dipinga lo schermo aggiornato prima di eseguire l’Effetto. Se il tuo Effetto fa qualcosa di visivo (ad esempio, posizionare un tooltip) e il ritardo è evidente (ad esempio, sfarfalla), sostituisci useEffect con useLayoutEffect.

  • Se il tuo Effetto è causato da un’interazione (come un click), React può eseguire l’Effetto prima che il browser dipinga lo schermo aggiornato. Questo garantisce che il risultato dell’Effetto possa essere osservato dal sistema di eventi. Di solito funziona come previsto. Tuttavia, se devi posticipare il lavoro fino a dopo il paint, come un alert(), puoi usare setTimeout. Vedi reactwg/react-18/128 per maggiori informazioni.

  • Anche se il tuo Effetto è stato causato da un’interazione (come un click), React può permettere al browser di ridipingere lo schermo prima di elaborare gli aggiornamenti di state all’interno dell’Effetto. Di solito funziona come previsto. Tuttavia, se devi impedire al browser di ridipingere lo schermo, devi sostituire useEffect con useLayoutEffect.

  • Gli Effetti vengono eseguiti solo sul client. Non vengono eseguiti durante la renderizzazione lato server.


Usage

Connettersi a un sistema esterno

Alcuni componenti devono restare connessi alla rete, a qualche API del browser o a una libreria di terze parti mentre sono visualizzati nella pagina. Questi sistemi non sono controllati da React, quindi vengono chiamati esterni.

Per connettere il tuo componente a un sistema esterno, chiama useEffect al top level del tuo componente:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}

Devi passare due argomenti a useEffect:

  1. Una funzione di setup con codice di setup che si connette a quel sistema.
    • Dovrebbe restituire una funzione di cleanup con codice di cleanup che si disconnette da quel sistema.
  2. Un elenco di dipendenze che include ogni valore del componente usato all’interno di quelle funzioni.

React chiama le funzioni di setup e cleanup quando è necessario, il che può accadere più volte:

  1. Il tuo codice di setup viene eseguito quando il componente viene aggiunto alla pagina (monta).
  2. Dopo ogni fase di commit del componente in cui le dipendenze sono cambiate:
    • Prima, il tuo codice di cleanup viene eseguito con le props e lo state precedenti.
    • Poi, il tuo codice di setup viene eseguito con le nuove props e il nuovo state.
  3. Il tuo codice di cleanup viene eseguito un’ultima volta dopo che il componente viene rimosso dalla pagina (smonta).

Illustriamo questa sequenza per l’esempio sopra.

Quando il componente ChatRoom sopra viene aggiunto alla pagina, si connetterà alla chat room con i valori iniziali di serverUrl e roomId. Se serverUrl o roomId cambiano a seguito di una fase di commit (ad esempio, se l’utente sceglie una chat room diversa in un menu a tendina), il tuo Effetto si disconnetterà dalla room precedente e si connetterà alla successiva. Quando il componente ChatRoom viene rimosso dalla pagina, il tuo Effetto si disconnetterà un’ultima volta.

Per aiutarti a trovare bug, in development React esegue setup e cleanup un’extra volta prima del setup. È un test di stress che verifica che la logica del tuo Effetto sia implementata correttamente. Se questo causa problemi visibili, alla funzione di cleanup manca della logica. La funzione di cleanup dovrebbe fermare o annullare ciò che faceva la funzione di setup. La regola generale è che l’utente non dovrebbe poter distinguere tra il setup chiamato una volta (come in production) e una sequenza setupcleanupsetup (come in development). Vedi soluzioni comuni.

Cerca di scrivere ogni Effetto come un processo indipendente e pensa a un singolo ciclo setup/cleanup alla volta. Non dovrebbe importare se il componente sta montando, aggiornando o smontando. Quando la logica di cleanup “specchia” correttamente la logica di setup, il tuo Effetto è resiliente all’esecuzione di setup e cleanup quanto spesso serve.

Nota bene

Un Effetto ti permette di mantenere il componente sincronizzato con un sistema esterno (come un servizio di chat). Qui, sistema esterno indica qualsiasi pezzo di codice non controllato da React, come:

Se non ti stai connettendo a nessun sistema esterno, probabilmente non ti serve un Effetto.

Esempi di connessione a un sistema esterno

Esempio 1 di 5:
Connessione a un server di chat

In questo esempio, il componente ChatRoom usa un Effetto per restare connesso a un sistema esterno definito in chat.js. Premi “Open chat” per far apparire il componente ChatRoom. Questa sandbox è in modalità development, quindi c’è un ciclo extra di connessione e disconnessione, come spiegato qui. Prova a cambiare roomId e serverUrl usando il menu a tendina e l’input, e osserva come l’Effetto si riconnette alla chat. Premi “Close chat” per vedere l’Effetto disconnettersi un’ultima volta.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId, serverUrl]);

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Close chat' : 'Open chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


Avvolgere gli Effetti in custom Hook

Gli Effetti sono una “via di fuga”: li usi quando devi “uscire da React” e quando non esiste una soluzione integrata migliore per il tuo caso d’uso. Se ti ritrovi spesso a scrivere Effetti manualmente, di solito è un segnale che devi estrarre alcuni custom Hook per i comportamenti comuni su cui si basano i tuoi componenti.

Per esempio, questo custom Hook useChatRoom “nasconde” la logica del tuo Effetto dietro un’API più dichiarativa:

function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]);
}

Poi puoi usarlo da qualsiasi componente così:

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useChatRoom({
roomId: roomId,
serverUrl: serverUrl
});
// ...

Ci sono anche molti ottimi custom Hook per ogni scopo disponibili nell’ecosistema React.

Scopri di più sull’avvolgere gli Effetti in custom Hook.

Esempi di avvolgimento degli Effetti in custom Hook

Esempio 1 di 3:
Custom Hook useChatRoom

Questo esempio è identico a uno degli esempi precedenti, ma la logica è estratta in un custom Hook.

import { useState } from 'react';
import { useChatRoom } from './useChatRoom.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useChatRoom({
    roomId: roomId,
    serverUrl: serverUrl
  });

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Close chat' : 'Open chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


Controllare un widget non-React

A volte vuoi mantenere un sistema esterno sincronizzato con una prop o lo state del tuo componente.

Per esempio, se hai un widget mappa di terze parti o un componente video player scritto senza React, puoi usare un Effetto per chiamare metodi su di esso che fanno corrispondere il suo state allo state attuale del tuo componente React. Questo Effetto crea un’istanza di una classe MapWidget definita in map-widget.js. Quando cambi la prop zoomLevel del componente Map, l’Effetto chiama setZoom() sull’istanza della classe per mantenerla sincronizzata:

import { useRef, useEffect } from 'react';
import { MapWidget } from './map-widget.js';

export default function Map({ zoomLevel }) {
  const containerRef = useRef(null);
  const mapRef = useRef(null);

  useEffect(() => {
    if (mapRef.current === null) {
      mapRef.current = new MapWidget(containerRef.current);
    }

    const map = mapRef.current;
    map.setZoom(zoomLevel);
  }, [zoomLevel]);

  return (
    <div
      style={{ width: 200, height: 200 }}
      ref={containerRef}
    />
  );
}

In questo esempio, una funzione di cleanup non è necessaria perché la classe MapWidget gestisce solo il nodo DOM che le è stato passato. Dopo che il componente React Map viene rimosso dall’albero, sia il nodo DOM che l’istanza della classe MapWidget verranno automaticamente garbage-collected dal motore JavaScript del browser.


Recuperare dati con gli Effetti

Puoi usare un Effetto per recuperare dati per il tuo componente. Nota che se usi un framework, usare il meccanismo di data fetching del framework sarà molto più efficiente che scrivere Effetti manualmente.

Se vuoi recuperare dati da un Effetto manualmente, il tuo codice potrebbe assomigliare a questo:

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
const [person, setPerson] = useState('Alice');
const [bio, setBio] = useState(null);

useEffect(() => {
let ignore = false;
setBio(null);
fetchBio(person).then(result => {
if (!ignore) {
setBio(result);
}
});
return () => {
ignore = true;
};
}, [person]);

// ...

Nota la variabile ignore che è inizializzata a false e viene impostata a true durante il cleanup. Questo garantisce che il codice non soffra di “race condition”: le risposte di rete possono arrivare in un ordine diverso da quello in cui le hai inviate.

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    let ignore = false;
    setBio(null);
    fetchBio(person).then(result => {
      if (!ignore) {
        setBio(result);
      }
    });
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Loading...'}</i></p>
    </>
  );
}

Puoi anche riscrivere usando la sintassi async / await, ma devi comunque fornire una funzione di cleanup:

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    async function startFetching() {
      setBio(null);
      const result = await fetchBio(person);
      if (!ignore) {
        setBio(result);
      }
    }

    let ignore = false;
    startFetching();
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Loading...'}</i></p>
    </>
  );
}

Scrivere il data fetching direttamente negli Effetti diventa ripetitivo e rende difficile aggiungere ottimizzazioni come caching e server rendering in seguito. È più semplice usare un custom Hook — il tuo o mantenuto dalla community.

Approfondimento

Quali sono buone alternative al data fetching negli Effetti?

Scrivere chiamate fetch all’interno degli Effetti è un modo popolare per recuperare dati, specialmente nelle app completamente client-side. Tuttavia, è un approccio molto manuale e ha svantaggi significativi:

  • Gli Effetti non vengono eseguiti sul server. Questo significa che l’HTML iniziale renderizzato lato server includerà solo uno state di caricamento senza dati. Il computer client dovrà scaricare tutto il JavaScript e renderizzare l’app solo per scoprire che ora deve caricare i dati. Non è molto efficiente.
  • Recuperare dati direttamente negli Effetti rende facile creare “network waterfall”. Renderizzi il componente padre, recuperi dei dati, renderizza i componenti figli, e poi iniziano a recuperare i loro dati. Se la rete non è molto veloce, è significativamente più lento rispetto a recuperare tutti i dati in parallelo.
  • Recuperare dati direttamente negli Effetti di solito significa che non precarichi o memorizzi nella cache i dati. Per esempio, se il componente smonta e poi rimonta, dovrebbe recuperare i dati di nuovo.
  • Non è molto ergonomico. C’è parecchio codice boilerplate quando scrivi chiamate fetch in modo che non soffra di bug come le race condition.

Questo elenco di svantaggi non è specifico di React. Si applica al recupero dati al mount con qualsiasi libreria. Come con il routing, il data fetching non è banale da fare bene, quindi raccomandiamo i seguenti approcci:

  • Se usi un framework, usa il suo meccanismo di data fetching integrato. I framework React moderni hanno meccanismi di data fetching integrati che sono efficienti e non soffrono delle insidie sopra.
  • Altrimenti, considera di usare o costruire una cache lato client. Soluzioni open source popolari includono TanStack Query, useSWR e React Router 6.4+. Puoi costruire anche la tua soluzione, nel qual caso useresti Effetti sotto il cofano ma aggiungeresti anche logica per deduplicare le richieste, memorizzare nella cache le risposte ed evitare network waterfall (precaricando i dati o sollevando i requisiti di dati alle route).

Puoi continuare a recuperare dati direttamente negli Effetti se nessuno di questi approcci ti conviene.


Specificare le dipendenze reattive

Nota che non puoi “scegliere” le dipendenze del tuo Effetto. Ogni valore reattivo usato dal codice del tuo Effetto deve essere dichiarato come dipendenza. L’elenco delle dipendenze del tuo Effetto è determinato dal codice circostante:

function ChatRoom({ roomId }) { // Questo è un valore reattivo
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // Anche questo è un valore reattivo

useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Questo Effetto legge questi valori reattivi
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]); // ✅ Quindi devi specificarli come dipendenze del tuo Effetto
// ...
}

Se serverUrl o roomId cambiano, il tuo Effetto si riconnetterà alla chat usando i nuovi valori.

I valori reattivi includono props e tutte le variabili e funzioni dichiarate direttamente all’interno del componente. Poiché roomId e serverUrl sono valori reattivi, non puoi rimuoverli dalle dipendenze. Se provi a ometterli e il tuo linter è configurato correttamente per React, il linter segnalerà questo come un errore da correggere:

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // 🔴 React Hook useEffect has missing dependencies: 'roomId' and 'serverUrl'
// ...
}

Per rimuovere una dipendenza, devi “dimostrare” al linter che non ha bisogno di essere una dipendenza. Per esempio, puoi spostare serverUrl fuori dal componente per dimostrare che non è reattivo e non cambierà alle ri-renderizzazioni:

const serverUrl = 'https://localhost:1234'; // Non è più un valore reattivo

function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Tutte le dipendenze dichiarate
// ...
}

Ora che serverUrl non è un valore reattivo (e non può cambiare a una ri-renderizzazione), non ha bisogno di essere una dipendenza. Se il codice del tuo Effetto non usa valori reattivi, il suo elenco di dipendenze dovrebbe essere vuoto ([]):

const serverUrl = 'https://localhost:1234'; // Non è più un valore reattivo
const roomId = 'music'; // Non è più un valore reattivo

function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ Tutte le dipendenze dichiarate
// ...
}

Un Effetto con dipendenze vuote non viene rieseguito quando cambiano le props o lo state del componente.

Insidia

Se hai una codebase esistente, potresti avere alcuni Effetti che sopprimono il linter così:

useEffect(() => {
// ...
// 🔴 Evita di sopprimere il linter così:
// eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);

Quando le dipendenze non corrispondono al codice, c’è un alto rischio di introdurre bug. Sopprimendo il linter, “menti” a React sui valori da cui dipende il tuo Effetto. Invece, dimostra che sono non necessarie.

Esempi di passaggio di dipendenze reattive

Esempio 1 di 3:
Passare un array di dipendenze

Se specifichi le dipendenze, il tuo Effetto viene eseguito dopo la fase di commit iniziale e dopo le fasi di commit con dipendenze cambiate.

useEffect(() => {
// ...
}, [a, b]); // Viene rieseguito se a o b sono diversi

Nell’esempio sotto, serverUrl e roomId sono valori reattivi, quindi entrambi devono essere specificati come dipendenze. Di conseguenza, selezionare una room diversa nel menu a tendina o modificare l’input dell’URL del server fa riconnettere la chat. Tuttavia, poiché message non è usato nell’Effetto (e quindi non è una dipendenza), modificare il messaggio non riconnette la chat.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');
  const [message, setMessage] = useState('');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [serverUrl, roomId]);

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
      <label>
        Your message:{' '}
        <input value={message} onChange={e => setMessage(e.target.value)} />
      </label>
    </>
  );
}

export default function App() {
  const [show, setShow] = useState(false);
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
        <button onClick={() => setShow(!show)}>
          {show ? 'Close chat' : 'Open chat'}
        </button>
      </label>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId}/>}
    </>
  );
}


Aggiornare lo state in base allo state precedente da un Effetto

Quando vuoi aggiornare lo state in base allo state precedente da un Effetto, potresti imbatterti in un problema:

function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1); // Vuoi incrementare il contatore ogni secondo...
}, 1000)
return () => clearInterval(intervalId);
}, [count]); // 🚩 ... ma specificare `count` come dipendenza resetta sempre l'intervallo.
// ...
}

Poiché count è un valore reattivo, deve essere specificato nell’elenco delle dipendenze. Tuttavia, ciò fa sì che l’Effetto esegua cleanup e setup di nuovo ogni volta che count cambia. Non è ideale.

Per risolvere, passa l’updater di state c => c + 1 a setCount:

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(c => c + 1); // ✅ Passa un updater di state
    }, 1000);
    return () => clearInterval(intervalId);
  }, []); // ✅ Ora count non è una dipendenza

  return <h1>{count}</h1>;
}

Ora che passi c => c + 1 invece di count + 1, il tuo Effetto non ha più bisogno di dipendere da count. Come risultato di questa correzione, non dovrà eseguire cleanup e setup dell’intervallo di nuovo ogni volta che count cambia.


Rimuovere dipendenze oggetto non necessarie

Se il tuo Effetto dipende da un oggetto o una funzione creati durante la renderizzazione, potrebbe essere eseguito troppo spesso. Per esempio, questo Effetto si riconnette dopo ogni fase di commit perché l’oggetto options è diverso a ogni renderizzazione:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

const options = { // 🚩 Questo oggetto viene creato da zero a ogni ri-renderizzazione
serverUrl: serverUrl,
roomId: roomId
};

useEffect(() => {
const connection = createConnection(options); // Viene usato all'interno dell'Effetto
connection.connect();
return () => connection.disconnect();
}, [options]); // 🚩 Di conseguenza, queste dipendenze sono sempre diverse a ogni fase di commit
// ...

Evita di usare un oggetto creato durante la renderizzazione come dipendenza. Invece, crea l’oggetto all’interno dell’Effetto:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Welcome to the {roomId} room!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Ora che crei l’oggetto options all’interno dell’Effetto, l’Effetto stesso dipende solo dalla stringa roomId.

Con questa correzione, digitare nell’input non riconnette la chat. A differenza di un oggetto che viene ricreato, una stringa come roomId non cambia a meno che non la imposti a un altro valore. Leggi di più sulla rimozione delle dipendenze.


Rimuovere dipendenze funzione non necessarie

Se il tuo Effetto dipende da un oggetto o una funzione creati durante la renderizzazione, potrebbe essere eseguito troppo spesso. Per esempio, questo Effetto si riconnette dopo ogni fase di commit perché la funzione createOptions è diversa a ogni renderizzazione:

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

function createOptions() { // 🚩 Questa funzione viene creata da zero a ogni ri-renderizzazione
return {
serverUrl: serverUrl,
roomId: roomId
};
}

useEffect(() => {
const options = createOptions(); // Viene usata all'interno dell'Effetto
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🚩 Di conseguenza, queste dipendenze sono sempre diverse a ogni fase di commit
// ...

Di per sé, creare una funzione da zero a ogni ri-renderizzazione non è un problema. Non devi ottimizzare quello. Tuttavia, se la usi come dipendenza del tuo Effetto, farà rieseguire l’Effetto dopo ogni fase di commit.

Evita di usare una funzione creata durante la renderizzazione come dipendenza. Invece, dichiarala all’interno dell’Effetto:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    function createOptions() {
      return {
        serverUrl: serverUrl,
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Welcome to the {roomId} room!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Ora che definisci la funzione createOptions all’interno dell’Effetto, l’Effetto stesso dipende solo dalla stringa roomId. Con questa correzione, digitare nell’input non riconnette la chat. A differenza di una funzione che viene ricreata, una stringa come roomId non cambia a meno che non la imposti a un altro valore. Leggi di più sulla rimozione delle dipendenze.


Leggere le props e lo state più recenti da un Effetto

Per impostazione predefinita, quando leggi un valore reattivo da un Effetto, devi aggiungerlo come dipendenza. Questo garantisce che il tuo Effetto “reagisca” a ogni cambiamento di quel valore. Per la maggior parte delle dipendenze, è il comportamento che vuoi.

Tuttavia, a volte vorrai leggere le props e lo state più recenti da un Effetto senza “reagire” ad essi. Per esempio, immagina di voler registrare il numero di articoli nel carrello per ogni visita alla pagina:

function Page({ url, shoppingCart }) {
useEffect(() => {
logVisit(url, shoppingCart.length);
}, [url, shoppingCart]); // ✅ Tutte le dipendenze dichiarate
// ...
}

E se volessi registrare una nuova visita alla pagina dopo ogni cambiamento di url, ma non se cambia solo shoppingCart? Non puoi escludere shoppingCart dalle dipendenze senza violare le regole di reattività. Tuttavia, puoi esprimere che non vuoi che un pezzo di codice “reagisca” ai cambiamenti anche se viene chiamato dall’interno di un Effetto. Dichiara un Effect Event con l’Hook useEffectEvent e sposta il codice che legge shoppingCart al suo interno:

function Page({ url, shoppingCart }) {
const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, shoppingCart.length)
});

useEffect(() => {
onVisit(url);
}, [url]); // ✅ Tutte le dipendenze dichiarate
// ...
}

Gli Effect Event non sono reattivi e devono sempre essere omessi dalle dipendenze del tuo Effetto. Questo ti permette di mettere codice non reattivo (dove puoi leggere il valore più recente di props e state) al loro interno. Leggendo shoppingCart all’interno di onVisit, garantisci che shoppingCart non farà rieseguire il tuo Effetto.

Leggi di più su come gli Effect Event ti permettono di separare codice reattivo e non reattivo.


Mostrare contenuti diversi sul server e sul client

Se la tua app usa la renderizzazione lato server (sia direttamente che tramite un framework), il componente verrà renderizzato in due ambienti diversi. Sul server, verrà renderizzato per produrre l’HTML iniziale. Sul client, React eseguirà di nuovo il codice di renderizzazione così da poter collegare i gestori di eventi a quell’HTML. Ecco perché, affinché l’hydration funzioni, l’output della renderizzazione iniziale deve essere identico sul client e sul server.

In casi rari, potresti aver bisogno di mostrare contenuti diversi sul client. Per esempio, se la tua app legge dei dati da localStorage, non può farlo sul server. Ecco come potresti implementarlo:

function MyComponent() {
const [didMount, setDidMount] = useState(false);

useEffect(() => {
setDidMount(true);
}, []);

if (didMount) {
// ... return client-only JSX ...
} else {
// ... return initial JSX ...
}
}

Mentre l’app si carica, l’utente vedrà l’output della renderizzazione iniziale. Poi, quando è caricata e idratata, il tuo Effetto verrà eseguito e imposterà didMount a true, avviando una ri-renderizzazione. Questo passerà all’output di renderizzazione solo client. Gli Effetti non vengono eseguiti sul server, ecco perché didMount era false durante la renderizzazione iniziale lato server.

Usa questo pattern con parsimonia. Tieni presente che gli utenti con una connessione lenta vedranno il contenuto iniziale per parecchio tempo — potenzialmente, molti secondi — quindi non vuoi fare cambiamenti bruschi all’aspetto del componente. In molti casi, puoi evitare la necessità di questo mostrando condizionalmente cose diverse con CSS.


Troubleshooting

Il mio Effetto viene eseguito due volte quando il componente monta

Quando Strict Mode è attivo, in development, React esegue setup e cleanup un’extra volta prima del setup effettivo.

È un test di stress che verifica che la logica del tuo Effetto sia implementata correttamente. Se questo causa problemi visibili, alla funzione di cleanup manca della logica. La funzione di cleanup dovrebbe fermare o annullare ciò che faceva la funzione di setup. La regola generale è che l’utente non dovrebbe poter distinguere tra il setup chiamato una volta (come in production) e una sequenza setup → cleanup → setup (come in development).

Leggi di più su come questo aiuta a trovare bug e come correggere la logica.


Il mio Effetto viene eseguito dopo ogni ri-renderizzazione

Per prima cosa, verifica di non aver dimenticato di specificare l’array di dipendenze:

useEffect(() => {
// ...
}); // 🚩 Nessun array di dipendenze: viene rieseguito dopo ogni fase di commit!

Se hai specificato l’array di dipendenze ma il tuo Effetto continua a rieseguirsi in loop, è perché una delle dipendenze è diversa a ogni ri-renderizzazione.

Puoi debuggare questo problema registrando manualmente le dipendenze nella console:

useEffect(() => {
// ..
}, [serverUrl, roomId]);

console.log([serverUrl, roomId]);

Poi puoi fare clic destro sugli array di ri-renderizzazioni diverse nella console e selezionare “Store as a global variable” per entrambi. Supponendo che il primo sia stato salvato come temp1 e il secondo come temp2, puoi usare la console del browser per verificare se ogni dipendenza in entrambi gli array è la stessa:

Object.is(temp1[0], temp2[0]); // La prima dipendenza è la stessa tra gli array?
Object.is(temp1[1], temp2[1]); // La seconda dipendenza è la stessa tra gli array?
Object.is(temp1[2], temp2[2]); // ... e così via per ogni dipendenza ...

Quando trovi la dipendenza che è diversa a ogni ri-renderizzazione, di solito puoi correggerla in uno di questi modi:

Come ultima risorsa (se questi metodi non hanno aiutato), avvolgi la sua creazione con useMemo o useCallback (per le funzioni).


Il mio Effetto continua a rieseguirsi in un ciclo infinito

Se il tuo Effetto viene eseguito in un ciclo infinito, queste due cose devono essere vere:

  • Il tuo Effetto sta aggiornando dello state.
  • Quello state porta a una ri-renderizzazione, che fa cambiare le dipendenze dell’Effetto.

Prima di iniziare a correggere il problema, chiediti se il tuo Effetto si sta connettendo a un sistema esterno (come DOM, rete, un widget di terze parti, e così via). Perché il tuo Effetto ha bisogno di impostare lo state? Si sincronizza con quel sistema esterno? O stai cercando di gestire il flusso di dati dell’applicazione con esso?

Se non c’è un sistema esterno, considera se rimuovere l’Effetto del tutto semplificherebbe la logica.

Se ti stai sincronizzando genuinamente con un sistema esterno, pensa a perché e in quali condizioni il tuo Effetto dovrebbe aggiornare lo state. È cambiato qualcosa che influisce sull’output visivo del componente? Se devi tenere traccia di dati non usati dalla renderizzazione, un ref (che non avvia ri-renderizzazioni) potrebbe essere più appropriato. Verifica che il tuo Effetto non aggiorni lo state (e avvii ri-renderizzazioni) più del necessario.

Infine, se il tuo Effetto aggiorna lo state al momento giusto, ma c’è ancora un loop, è perché quell’aggiornamento di state fa cambiare una delle dipendenze dell’Effetto. Leggi come debuggare i cambiamenti delle dipendenze.


La mia logica di cleanup viene eseguita anche se il componente non ha smontato

La funzione di cleanup viene eseguita non solo durante lo smontaggio, ma prima di ogni ri-renderizzazione con dipendenze cambiate. Inoltre, in development, React esegue setup+cleanup un’extra volta subito dopo il mount del componente.

Se hai codice di cleanup senza codice di setup corrispondente, di solito è un code smell:

useEffect(() => {
// 🔴 Evita: logica di cleanup senza logica di setup corrispondente
return () => {
doSomething();
};
}, []);

La logica di cleanup dovrebbe essere “simmetrica” alla logica di setup, e dovrebbe fermare o annullare ciò che ha fatto il setup:

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);

Scopri come il lifecycle dell’Effetto è diverso dal lifecycle del componente.


Il mio Effetto fa qualcosa di visivo e vedo uno sfarfallio prima che venga eseguito

Se il tuo Effetto deve impedire al browser di dipingere lo schermo, sostituisci useEffect con useLayoutEffect. Nota che non dovrebbe servire per la stragrande maggioranza degli Effetti. Ne avrai bisogno solo se è cruciale eseguire l’Effetto prima del paint del browser: per esempio, per misurare e posizionare un tooltip prima che l’utente lo veda.