Separando Eventos de Efeitos

Manipuladores de eventos só são executados novamente quando você realiza a mesma interação novamente. Ao contrário dos manipuladores de eventos, os Efeitos são re-sincronizados se algum valor que eles leem, como uma prop ou uma variável de estado, for diferente do que era durante a última renderização. Às vezes, você também quer uma mistura de ambos os comportamentos: um Efeito que é executado em resposta a alguns valores, mas não a outros. Esta página ensinará como fazer isso.

Você aprenderá

  • Como escolher entre um manipulador de eventos e um Efeito
  • Por que os Efeitos são reativos e os manipuladores de eventos não
  • O que fazer quando você quer que uma parte do código do seu Efeito não seja reativa
  • O que são Eventos de Efeito e como extraí-los dos seus Efeitos
  • Como ler as últimas props e estado dos Efeitos usando Eventos de Efeito

Escolhendo entre manipuladores de eventos e Efeitos

Primeiro, vamos revisar a diferença entre manipuladores de eventos e Efeitos.

Imagine que você está implementando um componente de chat. Seus requisitos são os seguintes:

  1. Seu componente deve se conectar automaticamente à sala de chat selecionada.
  2. Quando você clicar no botão “Enviar”, ele deve enviar uma mensagem para o chat.

Vamos supor que você já implementou o código para eles, mas não tem certeza de onde colocá-lo. Você deve usar manipuladores de eventos ou Efeitos? Sempre que precisar responder a essa pergunta, considere por que o código precisa ser executado.

Manipuladores de eventos são executados em resposta a interações específicas

Do ponto de vista do usuário, enviar uma mensagem deve acontecer porque o botão “Enviar” específico foi clicado. O usuário ficará bastante chateado se você enviar a mensagem dele em qualquer outro momento ou por qualquer outro motivo. É por isso que enviar uma mensagem deve ser um manipulador de eventos. Os manipuladores de eventos permitem que você trate interações específicas:

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

Com um manipulador de eventos, você pode ter certeza de que sendMessage(message) será executado apenas se o usuário pressionar o botão.

Efeitos são executados sempre que a sincronização é necessária

Lembre-se de que você também precisa manter o componente conectado à sala de chat. Onde esse código deve ir?

A razão para executar esse código não é alguma interação específica. Não importa como ou por que o usuário navegou até a tela da sala de chat. Agora que está olhando para ela e poderia interagir com ela, o componente precisa permanecer conectado ao servidor de chat selecionado. Mesmo que o componente da sala de chat fosse a tela inicial do seu aplicativo, e o usuário não tivesse realizado nenhuma interação, você ainda precisaria se conectar. É por isso que é um Efeito:

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

Com esse código, você pode ter certeza de que sempre há uma conexão ativa com o servidor de chat atualmente selecionado, independentemente das interações específicas realizadas pelo usuário. Quer o usuário tenha apenas aberto seu aplicativo, selecionado uma sala diferente, ou navegado para outra tela e voltado, seu Efeito garante que o componente permaneça sincronizado com a sala atualmente selecionada, e reconectará sempre que necessário.

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>Bem-vindo à sala {roomId}!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
      <button onClick={handleSendClick}>Enviar</button>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('geral');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="geral">geral</option>
          <option value="viagem">viagem</option>
          <option value="musica">música</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Fechar chat' : 'Abrir chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}

Valores reativos e lógica reativa

Intuitivamente, você poderia dizer que os manipuladores de eventos são sempre disparados “manualmente”, por exemplo, clicando em um botão. Os Efeitos, por outro lado, são “automáticos”: eles são executados e re-executados sempre que necessário para permanecerem sincronizados.

Há uma maneira mais precisa de pensar sobre isso.

Props, estado e variáveis declaradas dentro do corpo do seu componente são chamadas de valores reativos. Neste exemplo, serverUrl não é um valor reativo, mas roomId e message são. Eles participam do fluxo de dados de renderização:

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

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

// ...
}

Valores reativos como esses podem mudar devido a uma nova renderização. Por exemplo, o usuário pode editar a message ou escolher um roomId diferente em um dropdown. Manipuladores de eventos e Efeitos respondem às mudanças de maneira diferente:

  • A lógica dentro dos manipuladores de eventos não é reativa. Ela não será executada novamente, a menos que o usuário realize a mesma interação (por exemplo, um clique) novamente. Os manipuladores de eventos podem ler valores reativos sem “reagir” às suas mudanças.
  • A lógica dentro dos Efeitos é reativa. Se seu Efeito ler um valor reativo, você precisa especificá-lo como uma dependência. Então, se uma nova renderização fizer com que esse valor mude, o React re-executará a lógica do seu Efeito com o novo valor.

Vamos revisar o exemplo anterior para ilustrar essa diferença.

A lógica dentro dos manipuladores de eventos não é reativa

Veja esta linha de código. Essa lógica deve ser reativa ou não?

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

Do ponto de vista do usuário, uma mudança na message não significa que eles querem enviar uma mensagem. Isso apenas significa que o usuário está digitando. Em outras palavras, a lógica que envia uma mensagem não deve ser reativa. Ela não deve ser executada novamente apenas porque o valor reativo mudou. É por isso que pertence ao manipulador de eventos:

function handleSendClick() {
sendMessage(message);
}

Os manipuladores de eventos não são reativos, então sendMessage(message) só será executado quando o usuário clicar no botão Enviar.

A lógica dentro dos Efeitos é reativa

Agora vamos voltar para essas linhas:

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

Do ponto de vista do usuário, uma mudança no roomId significa que eles querem se conectar a uma sala diferente. Em outras palavras, a lógica para se conectar à sala deve ser reativa. Você quer que essas linhas de código “acompanhem” o valor reativo, e sejam executadas novamente se ese valor for diferente. É por isso que pertence a um Efeito:

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

Os Efeitos são reativos, então createConnection(serverUrl, roomId) e connection.connect() serão executados para cada valor distinto de roomId. Seu Efeito mantém a conexão de chat sincronizada com a sala atualmente selecionada.

Extraindo lógica não reativa dos Efeitos

As coisas ficam mais complicadas quando você deseja misturar lógica reativa com lógica não reativa.

Por exemplo, imagine que você quer mostrar uma notificação quando o usuário se conecta ao chat. Você lê o tema atual (claro ou escuro) das props para que possa mostrar a notificação na cor correta:

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

No entanto, theme é um valor reativo (pode mudar como resultado de uma nova renderização), e cada valor reativo lido por um Efeito deve ser declarado como sua dependência. Agora você precisa especificar theme como uma dependência do seu Efeito:

function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Conectado!', theme);
});
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId, theme]); // ✅ Todas as dependências declaradas
// ...

Brinque com este exemplo e veja se consegue identificar o problema com essa experiência do usuário:

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('Conectado!', theme);
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId, theme]);

  return <h1>Bem-vindo à sala {roomId}!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('geral');
  const [isDark, setIsDark] = useState(false);
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="geral">geral</option>
          <option value="viagem">viagem</option>
          <option value="musica">música</option>
        </select>
      </label>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Usar tema escuro
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'}
      />
    </>
  );
}

Quando o roomId muda, o chat reconecta como você esperaria. Mas como theme também é uma dependência, o chat também reconecta sempre que você alterna entre o tema escuro e o claro. Isso não é legal!

Em outras palavras, você não quer que esta linha seja reativa, mesmo que esteja dentro de um Efeito (que é reativo):

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

Você precisa de uma maneira de separar essa lógica não reativa da reativa ao seu redor.

Declarando um Evento de Efeito

Under Construction

Esta seção descreve uma API experimental que ainda não foi lançada em uma versão estável do React.

Use um Hook especial chamado useEffectEvent para extrair essa lógica não reativa dos seus Efeitos:

import { useEffect, useEffectEvent } from 'react';

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

Aqui, onConnected é chamado de um Evento de Efeito. É uma parte da lógica do seu Efeito, mas se comporta muito mais como um manipulador de eventos. A lógica dentro dele não é reativa, e sempre “vê” os valores mais recentes das suas props e estado.

Agora você pode chamar o Evento de Efeito onConnected de dentro do seu Efeito:

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

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Todas as dependências declaradas
// ...

Isso resolve o problema. Note que você teve que remover onConnected da lista de dependências do seu Efeito. Eventos de Efeito não são reativos e devem ser omitidos das dependências.

Verifique se o novo comportamento funciona como você esperaria:

import { useState, useEffect } from 'react';
import { experimental_useEffectEvent as 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('Conectado!', theme);
  });

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

  return <h1>Bem-vindo à sala {roomId}!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('geral');
  const [isDark, setIsDark] = useState(false);
  return (
    <>
      <label>
        Escolha a sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="geral">geral</option>
          <option value="viagem">viagem</option>
          <option value="musica">música</option>
        </select>
      </label>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Usar tema escuro
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'}
      />
    </>
  );
}

Você pode pensar nos Eventos de Efeito como sendo muito semelhantes aos manipuladores de eventos. A principal diferença é que os manipuladores de eventos são executados em resposta a interações do usuário, enquanto os Eventos de Efeito são acionados por você a partir dos Efeitos. Os Eventos de Efeito permitem que você “quebre a cadeia” entre a reatividade dos Efeitos e o código que não deve ser reativo.

Lendo as últimas props e estado com Eventos de Efeito

Under Construction

Esta seção descreve uma API experimental que ainda não foi lançada em uma versão estável do React.

Os Eventos de Efeito permitem que você conserte muitos padrões nos quais você pode estar tentado a suprimir o linter de dependência.

Por exemplo, digamos que você tem um Efeito para registrar as visitas à página:

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

Depois, você adiciona várias rotas ao seu site. Agora seu componente Page recebe uma prop url com o caminho atual. Você quer passar a url como parte do seu chamado de logVisit, mas o linter de dependências reclama:

function Page({ url }) {
useEffect(() => {
logVisit(url);
}, []); // 🔴 React Hook useEffect tem uma dependência ausente: 'url'
// ...
}

Pense sobre o que você quer que o código faça. Você quer registrar uma visita separada para diferentes URLs, pois cada URL representa uma página diferente. Em outras palavras, essa chamada de logVisit deve ser reativa em relação à url. É por isso que, nesse caso, faz sentido seguir o linter de dependências e adicionar url como uma dependência:

function Page({ url }) {
useEffect(() => {
logVisit(url);
}, [url]); // ✅ Todas as dependências declaradas
// ...
}

Agora digamos que você quer incluir o número de itens no carrinho de compras junto com cada visita à página:

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

useEffect(() => {
logVisit(url, numberOfItems);
}, [url]); // 🔴 React Hook useEffect tem uma dependência ausente: 'numberOfItems'
// ...
}

Você usou numberOfItems dentro do Efeito, então o linter pede que você a adicione como uma dependência. No entanto, você não quer que a chamada de logVisit seja reativa em relação a numberOfItems. Se o usuário coloca algo no carrinho de compras, e numberOfItems muda, isso não significa que o usuário visitou a página novamente. Em outras palavras, visitar a página é, em certo sentido, um “evento”. Acontece em um momento preciso no tempo.

Divida o código em duas partes:

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

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

useEffect(() => {
onVisit(url);
}, [url]); // ✅ Todas as dependências declaradas
// ...
}

Aqui, onVisit é um Evento de Efeito. O código dentro dele não é reativo. É por isso que você pode usar numberOfItems (ou qualquer outro valor reativo!) sem se preocupar que isso fará com que o código circundante seja reexecutado.

Por outro lado, o Efeito em si permanece reativo. O código dentro do Efeito usa a prop url, então o Efeito será executado após cada nova renderização com uma url diferente. Isso, por sua vez, chamará o Evento de Efeito onVisit.

Como resultado, você chamará logVisit para cada mudança na url, e sempre lerá a numberOfItems mais recente. No entanto, se numberOfItems mudar por conta própria, isso não fará com que nenhum dos códigos seja reexecutado.

Note

Você pode estar se perguntando se poderia chamar onVisit() sem argumentos e ler a url dentro dele:

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

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

Isso funcionaria, mas é melhor passar essa url explicitamente para o Evento de Efeito. Ao passar url como um argumento para seu Evento de Efeito, você está dizendo que visitar uma página com uma url diferente constitui um “evento” separado da perspectiva do usuário. A visitedUrl é uma parte do “evento” que aconteceu:

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

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

Como seu Evento de Efeito “pede” explicitamente pela visitedUrl, agora você não pode acidentalmente remover url das dependências do Efeito. Se você remover a dependência url (fazendo com que visitas distintas à página sejam contadas como uma), o linter alertará você sobre isso. Você quer que onVisit seja reativo em relação à url, então, em vez de ler a url dentro (onde não seria reativa), você a passa do seu Efeito.

Isso se torna especialmente importante se houver alguma lógica assíncrona dentro do Efeito:

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

useEffect(() => {
setTimeout(() => {
onVisit(url);
}, 5000); // Atraso ao registrar visitas
}, [url]);

Aqui, url dentro de onVisit corresponde à url mais recente (que já pode ter mudado), mas visitedUrl corresponde à url que originalmente fez com que esse Efeito (e essa chamada de onVisit) fosse executada.

Deep Dive

É aceitável suprimir o linter de dependência em vez disso?

Nos bases de código existentes, você pode às vezes ver a regra de lint suprimida assim:

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

useEffect(() => {
logVisit(url, numberOfItems);
// 🔴 Evite suprimir o linter assim:
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
// ...
}

Depois que useEffectEvent se tornar uma parte estável do React, recomendamos nunca suprimir o linter.

O primeiro problema de suprimir a regra é que o React não alertará mais você quando seu Efeito precisa “reagir” a uma nova dependência reativa que você introduziu em seu código. No exemplo anterior, você adicionou url às dependências porque o React lembrou você de fazer isso. Você não receberá mais tais lembretes para futuras edições desse Efeito se desativar o linter. Isso leva a bugs.

Aqui está um exemplo de um bug confuso causado pela supressão do linter. Neste exemplo, a função handleMove deve ler o valor atual da variável de estado canMove para decidir se o ponto deve seguir o cursor. No entanto, canMove sempre é true dentro de handleMove.

Você consegue ver por quê?

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)}
        />
        O ponto pode se mover
      </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,
      }} />
    </>
  );
}

O problema com esse código está em suprimir o linter de dependências. Se você remover a supressão, verá que este Efeito deve depender da função handleMove. Isso faz sentido: handleMove é declarada dentro do corpo do componente, o que a torna um valor reativo. Todo valor reativo deve ser especificado como uma dependência, ou poderá potencialmente ficar obsoleto com o tempo!

O autor do código original “mentiu” para o React ao dizer que o Efeito não depende ([]) de nenhum valor reativo. É por isso que o React não re-sincronizou o Efeito após canMove ter mudado (e handleMove com ele). Como o React não re-sincronizou o Efeito, o handleMove anexado como ouvinte é a função handleMove criada durante a renderização inicial. Durante a renderização inicial, canMove era true, e é por isso que handleMove da renderização inicial sempre verá esse valor.

Se você nunca suprimir o linter, você nunca verá problemas com valores obsoletos.

Com useEffectEvent, não há necessidade de “mentir” para o linter, e o código funciona como você esperaria:

import { useState, useEffect } from 'react';
import { experimental_useEffectEvent as 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)}
        />
        O ponto pode se mover
      </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,
      }} />
    </>
  );
}

Isso não significa que useEffectEvent é sempre a solução correta. Você deve aplicá-lo apenas nas linhas de código que você não quer que sejam reativas. No sandbox acima, você não queria que o código do Efeito fosse reativo em relação a canMove. É por isso que fez sentido extrair um Evento de Efeito.

Leia Removendo Dependências de Efeito para outras alternativas corretas à supressão do linter.

Limitações dos Eventos de Efeito

Under Construction

Esta seção descreve uma API experimental que ainda não foi lançada em uma versão estável do React.

Os Eventos de Efeito são muito limitados em como você pode usá-los:

  • Chame-os apenas de dentro de Efeitos.
  • Nunca os passe para outros componentes ou Hooks.

Por exemplo, não declare e passe um Evento de Efeito assim:

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

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

useTimer(onTick, 1000); // 🔴 Evite: Passando Eventos de Efeito

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

function useTimer(callback, delay) {
useEffect(() => {
const id = setInterval(() => {
callback();
}, delay);
return () => {
clearInterval(id);
};
}, [delay, callback]); // Necessita especificar "callback" nas dependências
}

Em vez disso, sempre declare Eventos de Efeito diretamente ao lado dos Efeitos que os usam:

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(); // ✅ Bom: Chamado apenas localmente dentro de um Efeito
}, delay);
return () => {
clearInterval(id);
};
}, [delay]); // Não é necessário especificar "onTick" (um Evento de Efeito) como uma dependência
}

Os Eventos de Efeito são “partes” não reativas do seu código de Efeito. Eles devem estar ao lado do Efeito que os usa.

Recap

  • Manipuladores de eventos são executados em resposta a interações específicas.
  • Efeitos são executados sempre que a sincronização é necessária.
  • A lógica dentro dos manipuladores de eventos não é reativa.
  • A lógica dentro dos Efeitos é reativa.
  • Você pode mover a lógica não reativa dos Efeitos para Eventos de Efeito.
  • Chame Eventos de Efeito apenas de dentro de Efeitos.
  • Não passe Eventos de Efeito para outros componentes ou Hooks.

Challenge 1 of 3:
Corrija uma variável que não se atualiza

Este componente Timer mantém uma variável de estado count que aumenta a cada segundo. O valor pelo qual está aumentando é armazenado na variável de estado increment. Você pode controlar a variável increment com os botões de mais e menos.

No entanto, não importa quantas vezes você clicar no botão de mais, o contador ainda é incrementado em um a cada segundo. O que há de errado com este código? Por que increment sempre é igual a 1 dentro do código do Efeito? Encontre o erro e corrija-o.

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>
        Contador: {count}
        <button onClick={() => setCount(0)}>Reiniciar</button>
      </h1>
      <hr />
      <p>
        A cada segundo, incrementar em:
        <button disabled={increment === 0} onClick={() => {
          setIncrement(i => i - 1);
        }}></button>
        <b>{increment}</b>
        <button onClick={() => {
          setIncrement(i => i + 1);
        }}>+</button>
      </p>
    </>
  );
}