Articolo originale: The React useEffect Hook for Absolute Beginners di Reed Barger

Tradotto e adattato da: Angelo Mirabelli

Se hai difficoltà a capire l'hook useEffect, non sei il solo.

Sia i principianti che gli sviluppatori esperti trovano che sia uno degli hook più difficili da capire, perché richiede la comprensione di alcuni concetti di programmazione non familiari.

In questa guida rapida, illustreremo il motivo per cui esiste questo hook, come comprenderlo meglio e come utilizzarlo correttamente oggi nei tuoi progetti React.

Perché si chiama useEffect?

Quando quando i principali Hook di React sono stati aggiunto alla libreria nel 2018 (useState, useEffect e così via), molti sviluppatori sono rimasti disorientati dal nome di questo hook: "useEffect".

Che cos'è esattamente un "effetto"?

La parola effetto si riferisce a un termine di programmazione funzionale chiamato "effetto collaterale" .

Ma per capire davvero cos'è un effetto collaterale, dobbiamo prima cogliere il concetto di funzione pura.

Potresti non saperlo, ma la maggior parte dei componenti di React sono pensati per essere funzioni pure.

Può essere strano pensare ai componenti di React come funzioni, ma lo sono.

Lo si capisce vedendo come un normale componente funzione di React venga dichiarato come una funzione JavaScript:

function MyReactComponent() {}

La maggior parte dei componenti React sono funzioni pure, il che significa che ricevono un input e producono un output prevedibile in JSX.

Per una funzione JavaScript l'input sono gli argomenti. Invece, qual è l'input per un componente React? Le proprietà!

Qui abbiamo un componente User che ha dichiarata una proprietà name. All'interno di User, il valore della proprietà viene visualizzato in un elemento di intestazione.

export default function App() {
  return <User name="John Doe" />   
}
  
function User(props) {
  return <h1>{props.name}</h1>; // John Doe
}

Questo è una funzione pura perché, dato lo stesso input, restituirà sempre lo stesso output.

Se passiamo a User una proprietà name con valore "John Doe", il nostro output sarà sempre John Doe.

Potresti dire: "Chi se ne frega? Perché c'è un nome anche per questo?"

Le funzioni pure hanno il grande vantaggio di essere prevedibili, affidabili e facili da testare.

Questo è comparato a quando dobbiamo eseguire un effetto collaterale nel nostro componente.

Quali sono gli effetti collaterali di React?

Gli effetti collaterali non sono prevedibili perché sono azioni che vengono eseguite con il "mondo esterno".

Eseguiamo un effetto collaterale quando dobbiamo raggiungere l'esterno dei nostri componenti React per fare qualcosa. L'esecuzione di un effetto collaterale, tuttavia, non ci darà un risultato prevedibile.

Pensa se dovessimo richiedere dati (come i post di un blog) da un server che ha fallito e invece dei dati dei nostri post, ci fornisce una risposta con codice di stato 500.

Praticamente tutte le applicazioni si basano sugli effetti collaterali per funzionare in un modo o nell'altro, a parte le applicazioni più semplici.

Gli effetti collaterali comuni includono:

  • Effettuare una richiesta a un'API per i dati da un server back-end
  • Interagire con le API del browser (ovvero  utilizzare direttamente document o window)
  • Utilizzare funzioni di temporizzazione imprevedibili come setTimeout o setInterval

Questo è il motivo per cui esiste useEffect: per fornire un modo per gestire l'esecuzione di questi effetti collaterali in quelli che altrimenti sarebbero componenti React puri.

Ad esempio, se volessimo modificare il tag meta del titolo per visualizzare il nome dell'utente nella scheda del browser, potremmo farlo all'interno del componente stesso, ma non dovremmo.

function User({ name }) {
  document.title = name; 
  // Questo è un effetto collaterale. Non farlo nel corpo del componente!
    
  return <h1>{name}</h1>;   
}

Se eseguiamo un effetto collaterale direttamente nel corpo del nostro componente, interferisce con il rendering del nostro componente React.

Gli effetti collaterali dovrebbero essere separati dal processo di rendering. Se abbiamo bisogno di eseguire un effetto collaterale, dovrebbe essere fatto rigorosamente dopo il rendering del nostro componente.

Questo è ciò che ci offre useEffect.

In breve, useEffect è uno strumento che ci consente di interagire con il mondo esterno ma non influisce sul rendering o sulle prestazioni del componente in cui si trova.

Come si usa useEffect?

La sintassi di base di useEffect è la seguente:

// 1. importa useEffect
import { useEffect } from 'react';

function MyComponent() {
  // 2. chiamalo prima che il JSX venga restituito
  // 3. passagli due argomenti: una funzione e un array
  useEffect(() => {}, []);
  
  // return ...
}

Il modo corretto per eseguire l'effetto collaterale nel nostro componente User è il seguente:

  1. Importiamo useEffect  da "react"
  2. Lo chiamiamo nel nostro componente prima che il JSX venga restituito
  3. Gli passiamo due argomenti: una funzione e un array
import { useEffect } from 'react';

function User({ name }) {
  useEffect(() => {
    document.title = name;
  }, [name]);
    
  return <h1>{name}</h1>;   
}

La funzione passata a useEffect è una funzione di callback che verrà chiamata dopo il rendering del componente.

In questa funzione possiamo eseguire l'effetto collaterale, o multipli effetti colllaterali se lo desideriamo.

Il secondo argomento è un array, chiamato array delle dipendenze, che dovrebbe includere tutti i valori su cui si basa il effetto collaterale.

Nel nostro esempio sopra, poiché stiamo cambiando il titolo in base a un valore nell'ambito esterno, name, dobbiamo includerlo all'interno dell'array delle dipendenze.

Quello che farà questo array è controllare e vedere se un valore (in questo caso il nome) è cambiato tra i rendering. In tal caso, eseguirà nuovamente la nostra funzione di utilizzo dell'effetto.

Questo ha senso perché se il nome cambia, vogliamo visualizzare il nuovo nome e quindi eseguire nuovamente il nostro effetto collaterale.

Come correggere gli errori comuni con useEffect

Ci sono alcuni dettagli sottili di cui essere consapevoli per evitare errori con useEffect.

Se non fornisci affatto l'array delle dipendenze e fornisci solo una funzione per useEffect, questa verrà eseguita dopo ogni rendering .

Ciò può causare problemi quando si tenta di aggiornare lo stato all'interno dell'hook useEffect.

Se dimentichi di fornire le tue dipendenze correttamente e stai impostando un pezzo di stato locale quando lo stato viene aggiornato, il comportamento predefinito di React è di rieseguire il rendering del componente. E quindi, poiché useEffect viene eseguito dopo ogni singolo rendering senza l'array delle dipendenze, avremo un ciclo infinito.

function MyComponent() {
  const [data, setData] = useState([])  
    
  useEffect(() => {
    fetchData().then(myData => setData(myData))
    // Errore! useEffect viene eseguito dopo ogni rendering senza l'array delle dipendenze, causando un ciclo infinito
  }); 
}

Dopo il primo rendering, useEffect verrà eseguito, lo stato verrà aggiornato, il che provocherà un re-render, che farà sì che useEffect venga eseguito nuovamente, riavviando il processo all'infinito.

Questo è chiamato un ciclo infinito e questo effettivamente interrompe la nostra applicazione.

Se stai aggiornando lo stato all'interno di useEffect, assicurati di fornire un array di dipendenze vuoto. Se fornisci un array vuoto, cosa che ti consiglio di fare per impostazione predefinita ogni volta che usi useEffect, ciò farà sì che la funzione dell'effetto venga eseguita solo una volta dopo che il componente è stato renderizzato la prima volta.

Un esempio comune per questo è il recupero dei dati. Per un componente, potresti voler recuperare i dati una volta, metterli nello stato e quindi visualizzarli nel tuo JSX.

function MyComponent() {
  const [data, setData] = useState([])  
    
  useEffect(() => {
    fetchData().then(myData => setData(myData))
    // Corretta! Viene eseguito una volta dopo il rendering con array vuoto
  }, []); 
   
  return <ul>{data.map(item => <li key={item}>{item}</li>)}</ul>
}

Qual è la funzione di pulizia in useEffect?

La parte finale dell'esecuzione corretta degli effetti collaterali in React è la funzione di pulizia degli effetti .

A volte i nostri effetti collaterali devono essere eliminati. Ad esempio, se hai un timer per il conto alla rovescia che utilizza la funzione setInterval, quell'intervallo non si interromperà a meno che non utilizziamo la funzione clearInterval.

Un altro esempio è utilizzare la sottoscrizione con WebSocket. Le sottoscrizioni devono essere "disattivate" quando non le utilizziamo più, ed è a questo che serve la funzione di pulizia.

Se stiamo impostando lo stato usando  setInterval e l'effetto collaterale non viene eliminato, quando il nostro componente si smonta e non lo stiamo più utilizzando, lo stato viene distrutto con il componente, ma la funzione setInterval continuerà a funzionare.

function Timer() {
  const [time, setTime] = useState(0);
    
  useEffect(() => {
    setInterval(() => setTime(1), 1000); 
    // conta fino a 1 ogni secondo
    // dobbiamo smettere di usare setInterval quando il componente viene smontato
  }, []);
}

Il problema con questo se il componente si sta distruggendo, è che setInterval proverà ad aggiornare una variabile, un pezzo di stato time che non esiste più. Questo è un errore chiamato perdita di memoria.

Per utilizzare la funzione di pulizia, è necessario restituire una funzione dall'interno della funzione useEffect.

All'interno di questa funzione possiamo eseguire la nostra pulizia, in questo caso utilizzare clearInterval e interrompere setInterval.

function Timer() {
  const [time, setTime] = useState(0);
    
  useEffect(() => {
    let interval = setInterval(() => setTime(1), 1000); 

    return () => {
      // setInterval azzerato quando il componente viene smontato
      clearInterval(interval);
    }
  }, []);
}

La funzione di pulizia verrà chiamata quando il componente viene smontato.

Un esempio comune di smontaggio di un componente è il passaggio a una nuova pagina o un nuovo percorso nella nostra applicazione in cui il componente non viene più visualizzato.

Quando un componente viene smontato, la nostra funzione di pulizia viene eseguita, il nostro intervallo viene cancellato e non viene più visualizzato un errore nel tentativo di aggiornare una variabile di stato che non esiste.

Infine, la pulizia degli effetti collaterali non è richiesta in tutti i casi. È richiesto solo in alcuni casi, ad esempio quando è necessario interrompere un effetto collaterale ripetuto quando il componente si smonta.