Separare eventi ed Effetti

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.

I gestori di eventi vengono rieseguiti solo quando ripeti la stessa interazione. A differenza dei gestori di eventi, gli Effetti si re-sincronizzano se un valore che leggono, come una prop o una variabile di state, è diverso rispetto all’ultima renderizzazione. A volte vuoi anche un mix di entrambi i comportamenti: un Effetto che si riesegue in risposta ad alcuni valori ma non ad altri. Questa pagina ti insegna come fare.

Imparerai

  • Come scegliere tra un gestore di eventi e un Effetto
  • Perché gli Effetti sono reattivi e i gestori di eventi no
  • Cosa fare quando vuoi che una parte del codice del tuo Effetto non sia reattiva
  • Cosa sono gli Effect Event e come estrarli dai tuoi Effetti
  • Come leggere le props e lo state più recenti dagli Effetti usando gli Effect Event

Scegliere tra gestori di eventi ed Effetti

Per prima cosa, riassumiamo la differenza tra gestori di eventi ed Effetti.

Immagina di implementare un componente chat room. I tuoi requisiti sono questi:

  1. Il tuo componente dovrebbe connettersi automaticamente alla chat room selezionata.
  2. Quando clicchi il pulsante “Send”, dovrebbe inviare un messaggio alla chat.

Supponiamo che tu abbia già implementato il codice per entrambi, ma non sei sicuro dove metterlo. Dovresti usare gestori di eventi o Effetti? Ogni volta che devi rispondere a questa domanda, considera perché il codice deve essere eseguito.

I gestori di eventi vengono eseguiti in risposta a interazioni specifiche

Dal punto di vista dell’utente, l’invio di un messaggio dovrebbe avvenire perché è stato cliccato il particolare pulsante “Send”. L’utente si arrabbierebbe se inviassi il suo messaggio in qualsiasi altro momento o per qualsiasi altra ragione. Ecco perché l’invio di un messaggio dovrebbe essere un gestore di eventi. I gestori di eventi ti permettono di gestire interazioni specifiche:

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
// ...
function handleSendClick() {
sendMessage(message);
}
// ...
return (
<>
<input value={message} onChange={e => setMessage(e.target.value)} />
<button onClick={handleSendClick}>Send</button>
</>
);
}

Con un gestore di eventi, puoi essere sicuro che sendMessage(message) verrà eseguito solo se l’utente preme il pulsante.

Gli Effetti vengono eseguiti quando serve la sincronizzazione

Ricorda che devi anche mantenere il componente connesso alla chat room. Dove va quel codice?

Il motivo per eseguire questo codice non è una particolare interazione. Non importa perché o come l’utente è arrivato alla schermata della chat room. Ora che la sta guardando e potrebbe interagirci, il componente deve restare connesso al server di chat selezionato. Anche se il componente chat room fosse la schermata iniziale della tua app e l’utente non avesse compiuto alcuna interazione, dovresti comunque connetterti. Ecco perché è un Effetto:

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

Con questo codice, puoi essere sicuro che c’è sempre una connessione attiva al server di chat attualmente selezionato, indipendentemente dalle interazioni specifiche compiute dall’utente. Che l’utente abbia solo aperto la tua app, selezionato una stanza diversa o navigato verso un’altra schermata e poi tornato indietro, il tuo Effetto garantisce che il componente resti sincronizzato con la stanza attualmente selezionata e si ricollegherà ogni volta che è necessario.

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

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

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

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

  function handleSendClick() {
    sendMessage(message);
  }

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

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} />}
    </>
  );
}

Valori reattivi e logica reattiva

Intuitivamente, potresti dire che i gestori di eventi vengono sempre attivati “manualmente”, per esempio cliccando un pulsante. Gli Effetti, invece, sono “automatici”: vengono eseguiti e rieseguiti quanto spesso serve per restare sincronizzati.

C’è un modo più preciso di pensarci.

Props, state e variabili dichiarate nel corpo del componente si chiamano valori reattivi. In questo esempio, serverUrl non è un valore reattivo, ma roomId e message lo sono. Partecipano al flusso di dati della renderizzazione:

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

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

// ...
}

Valori reattivi come questi possono cambiare a causa di una ri-renderizzazione. Per esempio, l’utente può modificare il message o scegliere un roomId diverso in un menu a tendina. Gestori di eventi ed Effetti rispondono ai cambiamenti in modo diverso:

  • La logica all’interno dei gestori di eventi non è reattiva. Non verrà rieseguita a meno che l’utente non compia di nuovo la stessa interazione (per esempio, un click). I gestori di eventi possono leggere valori reattivi senza “reagire” ai loro cambiamenti.
  • La logica all’interno degli Effetti è reattiva. Se il tuo Effetto legge un valore reattivo, devi specificarlo come dipendenza. Poi, se una ri-renderizzazione fa cambiare quel valore, React rieseguirà la logica del tuo Effetto con il nuovo valore.

Rivediamo l’esempio precedente per illustrare questa differenza.

La logica all’interno dei gestori di eventi non è reattiva

Dai un’occhiata a questa riga di codice. Questa logica dovrebbe essere reattiva o no?

// ...
sendMessage(message);
// ...

Dal punto di vista dell’utente, un cambiamento al message non significa che vogliono inviare un messaggio. Significa solo che l’utente sta digitando. In altre parole, la logica che invia un messaggio non dovrebbe essere reattiva. Non dovrebbe rieseguirsi solo perché il valore reattivo è cambiato. Ecco perché appartiene al gestore di eventi:

function handleSendClick() {
sendMessage(message);
}

I gestori di eventi non sono reattivi, quindi sendMessage(message) verrà eseguito solo quando l’utente clicca il pulsante Send.

La logica all’interno degli Effetti è reattiva

Ora torniamo a queste righe:

// ...
const connection = createConnection(serverUrl, roomId);
connection.connect();
// ...

Dal punto di vista dell’utente, un cambiamento al roomId significa che vogliono connettersi a una stanza diversa. In altre parole, la logica per connettersi alla stanza dovrebbe essere reattiva. Vuoi che queste righe di codice “tengano il passo” con il valore reattivo e vengano rieseguite se quel valore è diverso. Ecco perché appartiene a un Effetto:

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

Gli Effetti sono reattivi, quindi createConnection(serverUrl, roomId) e connection.connect() verranno eseguiti per ogni valore distinto di roomId. Il tuo Effetto mantiene la connessione alla chat sincronizzata con la stanza attualmente selezionata.

Estrarre la logica non reattiva dagli Effetti

Le cose si complicano quando vuoi mescolare logica reattiva con logica non reattiva.

Per esempio, immagina di voler mostrare una notifica quando l’utente si connette alla chat. Leggi il tema attuale (scuro o chiaro) dalle props così da poter mostrare la notifica nel colore corretto:

function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
// ...

Tuttavia, theme è un valore reattivo (può cambiare a causa di una ri-renderizzazione) e ogni valore reattivo letto da un Effetto deve essere dichiarato come sua dipendenza. Ora devi specificare theme come dipendenza del tuo Effetto:

function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId, theme]); // ✅ All dependencies declared
// ...

Gioca con questo esempio e vedi se riesci a individuare il problema con questa esperienza utente:

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

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

function ChatRoom({ roomId, theme }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      showNotification('Connected!', theme);
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId, theme]);

  return <h1>Welcome to the {roomId} room!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [isDark, setIsDark] = 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>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Use dark theme
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'}
      />
    </>
  );
}

Quando roomId cambia, la chat si ricollega come ti aspetteresti. Ma poiché theme è anche una dipendenza, la chat si ricollega anche ogni volta che passi dal tema scuro a quello chiaro. Non è il massimo!

In altre parole, non vuoi che questa riga sia reattiva, anche se si trova all’interno di un Effetto (che è reattivo):

// ...
showNotification('Connected!', theme);
// ...

Hai bisogno di un modo per separare questa logica non reattiva dall’Effetto reattivo che la circonda.

Dichiarare un Effect Event

Usa un Hook speciale chiamato useEffectEvent per estrarre questa logica non reattiva dal tuo Effetto:

import { useEffect, useEffectEvent } from 'react';

function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
// ...

Qui, onConnected si chiama un Effect Event. Fa parte della logica del tuo Effetto, ma si comporta molto più come un gestore di eventi. La logica al suo interno non è reattiva e “vede” sempre i valori più recenti delle tue props e del tuo state.

Ora puoi chiamare l’Effect Event onConnected dall’interno del tuo Effetto:

function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ All dependencies declared
// ...

Questo risolve il problema. Nota che hai dovuto rimuovere theme dall’elenco delle dipendenze del tuo Effetto, perché non è più usato nell’Effetto. Non devi neanche aggiungere onConnected, perché gli Effect Event non sono reattivi e devono essere omessi dalle dipendenze.

Verifica che il nuovo comportamento funzioni come ti aspetteresti:

import { useState, useEffect } from 'react';
import { useEffectEvent } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';

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

function ChatRoom({ roomId, theme }) {
  const onConnected = useEffectEvent(() => {
    showNotification('Connected!', theme);
  });

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

  return <h1>Welcome to the {roomId} room!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [isDark, setIsDark] = 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>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Use dark theme
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'}
      />
    </>
  );
}

Puoi pensare agli Effect Event come molto simili ai gestori di eventi. La differenza principale è che i gestori di eventi vengono eseguiti in risposta a interazioni dell’utente, mentre gli Effect Event vengono attivati da te dagli Effetti. Gli Effect Event ti permettono di “rompere la catena” tra la reattività degli Effetti e il codice che non dovrebbe essere reattivo.

Leggere le props e lo state più recenti con gli Effect Event

Gli Effect Event ti permettono di correggere molti pattern in cui potresti essere tentato di sopprimere il linter delle dipendenze.

Per esempio, supponiamo che tu abbia un Effetto per registrare le visite alle pagine:

function Page() {
useEffect(() => {
logVisit();
}, []);
// ...
}

In seguito, aggiungi più route al tuo sito. Ora il tuo componente Page riceve una prop url con il percorso attuale. Vuoi passare l’url come parte della tua chiamata logVisit, ma il linter delle dipendenze si lamenta:

function Page({ url }) {
useEffect(() => {
logVisit(url);
}, []); // 🔴 React Hook useEffect has a missing dependency: 'url'
// ...
}

Pensa a cosa vuoi che faccia il codice. Vuoi registrare una visita separata per URL diversi, poiché ogni URL rappresenta una pagina diversa. In altre parole, questa chiamata logVisit dovrebbe essere reattiva rispetto all’url. Ecco perché, in questo caso, ha senso seguire il linter delle dipendenze e aggiungere url come dipendenza:

function Page({ url }) {
useEffect(() => {
logVisit(url);
}, [url]); // ✅ All dependencies declared
// ...
}

Ora supponiamo che tu voglia includere il numero di articoli nel carrello insieme a ogni visita alla pagina:

function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;

useEffect(() => {
logVisit(url, numberOfItems);
}, [url]); // 🔴 React Hook useEffect has a missing dependency: 'numberOfItems'
// ...
}

Hai usato numberOfItems all’interno dell’Effetto, quindi il linter ti chiede di aggiungerlo come dipendenza. Tuttavia, non vuoi che la chiamata logVisit sia reattiva rispetto a numberOfItems. Se l’utente mette qualcosa nel carrello e numberOfItems cambia, questo non significa che l’utente ha visitato di nuovo la pagina. In altre parole, visitare la pagina è, in un certo senso, un “evento”. Avviene in un momento preciso nel tempo.

Dividi il codice in due parti:

function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;

const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, numberOfItems);
});

useEffect(() => {
onVisit(url);
}, [url]); // ✅ All dependencies declared
// ...
}

Qui, onVisit è un Effect Event. Il codice al suo interno non è reattivo. Ecco perché puoi usare numberOfItems (o qualsiasi altro valore reattivo!) senza preoccuparti che causerà la riesecuzione del codice circostante al cambiamento.

D’altra parte, l’Effetto stesso resta reattivo. Il codice all’interno dell’Effetto usa la prop url, quindi l’Effetto si rieseguirà dopo ogni ri-renderizzazione con un url diverso. Questo, a sua volta, chiamerà l’Effect Event onVisit.

Di conseguenza, chiamerai logVisit per ogni cambiamento all’url e leggerai sempre l’numberOfItems più recente. Tuttavia, se numberOfItems cambia da solo, questo non causerà la riesecuzione di nessun codice.

Nota bene

Potresti chiederti se potresti chiamare onVisit() senza argomenti e leggere l’url al suo interno:

const onVisit = useEffectEvent(() => {
logVisit(url, numberOfItems);
});

useEffect(() => {
onVisit();
}, [url]);

Funzionerebbe, ma è meglio passare questo url all’Effect Event esplicitamente. Passando url come argomento al tuo Effect Event, stai dicendo che visitare una pagina con un url diverso costituisce un “evento” separato dal punto di vista dell’utente. L’visitedUrl è una parte dell‘“evento” che è accaduto:

const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, numberOfItems);
});

useEffect(() => {
onVisit(url);
}, [url]);

Poiché il tuo Effect Event “chiede” esplicitamente l’visitedUrl, ora non puoi rimuovere accidentalmente url dalle dipendenze dell’Effetto. Se rimuovi la dipendenza url (facendo sì che visite a pagine distinte vengano contate come una sola), il linter ti avviserà. Vuoi che onVisit sia reattivo rispetto all’url, quindi invece di leggere l’url all’interno (dove non sarebbe reattivo), lo passi dal tuo Effetto.

Questo diventa particolarmente importante se c’è della logica asincrona all’interno dell’Effetto:

const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, numberOfItems);
});

useEffect(() => {
setTimeout(() => {
onVisit(url);
}, 5000); // Delay logging visits
}, [url]);

Qui, l’url all’interno di onVisit corrisponde all’url più recente (che potrebbe essere già cambiato), ma visitedUrl corrisponde all’url che ha originariamente causato l’esecuzione di questo Effetto (e di questa chiamata onVisit).

Approfondimento

Va bene sopprimere il linter delle dipendenze?

Nei codebase esistenti, a volte potresti vedere la regola del linter soppressa così:

function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;

useEffect(() => {
logVisit(url, numberOfItems);
// 🔴 Avoid suppressing the linter like this:
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
// ...
}

Consigliamo di non sopprimere mai il linter.

Il primo svantaggio di sopprimere la regola è che React non ti avviserà più quando il tuo Effetto deve “reagire” a una nuova dipendenza reattiva che hai introdotto nel tuo codice. Nell’esempio precedente, hai aggiunto url alle dipendenze perché React te lo ha ricordato. Non riceverai più questi promemoria per future modifiche a quell’Effetto se disabiliti il linter. Questo porta a bug.

Ecco un esempio di un bug confuso causato dalla soppressione del linter. In questo esempio, la funzione handleMove dovrebbe leggere il valore attuale della variabile di state canMove per decidere se il punto deve seguire il cursore. Tuttavia, canMove è sempre true all’interno di handleMove.

Riesci a capire perché?

import { useState, useEffect } from 'react';

export default function App() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [canMove, setCanMove] = useState(true);

  function handleMove(e) {
    if (canMove) {
      setPosition({ x: e.clientX, y: e.clientY });
    }
  }

  useEffect(() => {
    window.addEventListener('pointermove', handleMove);
    return () => window.removeEventListener('pointermove', handleMove);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      <label>
        <input type="checkbox"
          checked={canMove}
          onChange={e => setCanMove(e.target.checked)}
        />
        The dot is allowed to move
      </label>
      <hr />
      <div style={{
        position: 'absolute',
        backgroundColor: 'pink',
        borderRadius: '50%',
        opacity: 0.6,
        transform: `translate(${position.x}px, ${position.y}px)`,
        pointerEvents: 'none',
        left: -20,
        top: -20,
        width: 40,
        height: 40,
      }} />
    </>
  );
}

Il problema con questo codice è nella soppressione del linter delle dipendenze. Se rimuovi la soppressione, vedrai che questo Effetto dovrebbe dipendere dalla funzione handleMove. Ha senso: handleMove è dichiarata nel corpo del componente, il che la rende un valore reattivo. Ogni valore reattivo deve essere specificato come dipendenza, altrimenti può diventare obsoleto nel tempo!

L’autore del codice originale ha “mentito” a React dicendo che l’Effetto non dipende ([]) da alcun valore reattivo. Ecco perché React non ha re-sincronizzato l’Effetto dopo che canMove è cambiato (e handleMove con esso). Poiché React non ha re-sincronizzato l’Effetto, il handleMove attaccato come listener è la funzione handleMove creata durante la renderizzazione iniziale. Durante la renderizzazione iniziale, canMove era true, ecco perché handleMove dalla renderizzazione iniziale vedrà per sempre quel valore.

Se non sopprimi mai il linter, non vedrai mai problemi con valori obsoleti.

Con useEffectEvent, non c’è bisogno di “mentire” al linter e il codice funziona come ti aspetteresti:

import { useState, useEffect } from 'react';
import { useEffectEvent } from 'react';

export default function App() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [canMove, setCanMove] = useState(true);

  const onMove = useEffectEvent(e => {
    if (canMove) {
      setPosition({ x: e.clientX, y: e.clientY });
    }
  });

  useEffect(() => {
    window.addEventListener('pointermove', onMove);
    return () => window.removeEventListener('pointermove', onMove);
  }, []);

  return (
    <>
      <label>
        <input type="checkbox"
          checked={canMove}
          onChange={e => setCanMove(e.target.checked)}
        />
        The dot is allowed to move
      </label>
      <hr />
      <div style={{
        position: 'absolute',
        backgroundColor: 'pink',
        borderRadius: '50%',
        opacity: 0.6,
        transform: `translate(${position.x}px, ${position.y}px)`,
        pointerEvents: 'none',
        left: -20,
        top: -20,
        width: 40,
        height: 40,
      }} />
    </>
  );
}

Questo non significa che useEffectEvent sia sempre la soluzione corretta. Dovresti applicarlo solo alle righe di codice che non vuoi siano reattive. Nella sandbox sopra, non volevi che il codice dell’Effetto fosse reattivo rispetto a canMove. Ecco perché aveva senso estrarre un Effect Event.

Leggi Rimuovere le dipendenze degli Effetti per altre alternative corrette alla soppressione del linter.

Limitazioni degli Effect Event

Gli Effect Event sono molto limitati nel modo in cui puoi usarli:

  • Chiamali solo dall’interno degli Effetti.
  • Non passarli mai ad altri componenti o Hooks.

Per esempio, non dichiarare e passare un Effect Event così:

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

const onTick = useEffectEvent(() => {
setCount(count + 1);
});

useTimer(onTick, 1000); // 🔴 Avoid: Passing Effect Events

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

function useTimer(callback, delay) {
useEffect(() => {
const id = setInterval(() => {
callback();
}, delay);
return () => {
clearInterval(id);
};
}, [delay, callback]); // Need to specify "callback" in dependencies
}

Invece, dichiara sempre gli Effect Event direttamente accanto agli Effetti che li usano:

function Timer() {
const [count, setCount] = useState(0);
useTimer(() => {
setCount(count + 1);
}, 1000);
return <h1>{count}</h1>
}

function useTimer(callback, delay) {
const onTick = useEffectEvent(() => {
callback();
});

useEffect(() => {
const id = setInterval(() => {
onTick(); // ✅ Good: Only called locally inside an Effect
}, delay);
return () => {
clearInterval(id);
};
}, [delay]); // No need to specify "onTick" (an Effect Event) as a dependency
}

Gli Effect Event sono “pezzi” non reattivi del codice del tuo Effetto. Dovrebbero stare accanto all’Effetto che li usa.

Riepilogo

  • I gestori di eventi vengono eseguiti in risposta a interazioni specifiche.
  • Gli Effetti vengono eseguiti quando serve la sincronizzazione.
  • La logica all’interno dei gestori di eventi non è reattiva.
  • La logica all’interno degli Effetti è reattiva.
  • Puoi spostare la logica non reattiva dagli Effetti negli Effect Event.
  • Chiama gli Effect Event solo dall’interno degli Effetti.
  • Non passare gli Effect Event ad altri componenti o Hooks.

Sfida 1 di 4:
Correggi una variabile che non si aggiorna

Questo componente Timer mantiene una variabile di state count che aumenta ogni secondo. Il valore di cui aumenta è memorizzato nella variabile di state increment. Puoi controllare la variabile increment con i pulsanti più e meno.

Tuttavia, non importa quante volte clicchi il pulsante più, il contatore viene ancora incrementato di uno ogni secondo. Cosa c’è di sbagliato in questo codice? Perché increment è sempre uguale a 1 all’interno del codice dell’Effetto? Trova l’errore e correggilo.

import { useState, useEffect } from 'react';

export default function Timer() {
  const [count, setCount] = useState(0);
  const [increment, setIncrement] = useState(1);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + increment);
    }, 1000);
    return () => {
      clearInterval(id);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      <h1>
        Counter: {count}
        <button onClick={() => setCount(0)}>Reset</button>
      </h1>
      <hr />
      <p>
        Every second, increment by:
        <button disabled={increment === 0} onClick={() => {
          setIncrement(i => i - 1);
        }}>–</button>
        <b>{increment}</b>
        <button onClick={() => {
          setIncrement(i => i + 1);
        }}>+</button>
      </p>
    </>
  );
}