Effects
Imagine um componente que precisa manter um timer marcando após ser renderizado, como faz o componente Timer abaixo. A maior parte do que um componente faz acontece enquanto ele renderiza: lê props e state e retorna JSX. Manter um timer rodando é diferente. O mesmo vale para abrir uma subscription, sincronizar o título do documento ou buscar dados de um servidor. Cada uma dessas operações alcança o mundo externo para tocar em algo que React não gerencia. useEffect é o hook para isso. Ele permite que um componente execute algum código após renderizar, para que se sincronize com aquela coisa externa.
Aqui está um effect que inicia o timer uma única vez e incrementa um contador a cada segundo:
import { useState, useEffect } from 'react'
function Timer() {
const [count, setCount] = useState(0)
useEffect(() => {
const id = setInterval(() => setCount(c => c + 1), 1000)
return () => clearInterval(id)
}, [])
return <p>{count}</p>
}Três partes fazem o trabalho aqui, e elas correspondem às três coisas com que todo effect lida: o que fazer, como limpar e quando executar.
A função que você passa para useEffect é o effect em si. Este chama setInterval para iniciar um timer repetido que incrementa count em uma unidade a cada segundo.
O array de dependências
O segundo argumento, o [] no final, é o array de dependências. Ele controla quando o effect executa.
- Um array vazio
[]significa que o effect executa uma única vez, após o componente aparecer na tela pela primeira vez. O timer acima usa isso: inicia o intervalo uma vez e o deixa rodando. - Um array com valores, como
[a, b], significa que o effect executa após a primeira renderização e novamente sempre que um desses valores mudar entre renderizações. Assim você ressincroniza quando algo de que o effect depende foi alterado. - Omitir o array inteiramente significa que o effect executa após cada renderização. Raramente é isso que você quer, e é uma fonte comum de loops descontrolados quando o effect também atualiza state.
Então o array é sua resposta à pergunta "quando esse código deve executar novamente?" Liste os valores que o effect lê e de que depende, e React o executará novamente quando qualquer um deles mudar.
Funções de limpeza
O effect acima retorna uma função:
return () => clearInterval(id)Essa função retornada é a função de limpeza. React a executa antes do effect rodar novamente, e mais uma vez quando o componente é removido da tela. Seu trabalho é desfazer o que o effect configurou.
A limpeza importa porque effects frequentemente iniciam algo que continua por conta própria: um intervalo, um event listener, uma conexão aberta. Se o componente é removido e nada para aquele intervalo, ele continua disparando para sempre e mantém uma referência a um componente que já se foi. Faça isso algumas vezes e você tem um vazamento lento com atualizações chegando a coisas que não existem mais. A regra de ouro: se um effect inicia algo, inscreve-se em algo ou abre algo, sua função de limpeza deve parar, desinscrever-se ou fechar isso.
Você pode não precisar de um effect
Effects servem para sincronizar com o mundo externo, então muito código que parece candidato para useEffect pertence a outro lugar. Dois casos aparecem constantemente.
O primeiro é valores derivados. Se você consegue calcular algo a partir de props e state que você já tem, calcule durante a renderização:
function Cart({ items }) {
const total = items.reduce((sum, item) => sum + item.price, 0)
return <p>Total: {total}</p>
}total é derivado de items, então é calculado a cada renderização e está sempre sincronizado. Armazená-lo em seu próprio state e atualizá-lo a partir de um effect seria mais código e uma renderização extra sem ganho.
O segundo é responder a uma ação do usuário. Quando algo deve acontecer porque o usuário clicou ou digitou, esse código vai no event handler daquela ação:
function BuyButton({ product }) {
function handleClick() {
buyProduct(product)
}
return <button onClick={handleClick}>Buy</button>
}A compra acontece em handleClick, bem onde o clique é tratado. Encaminhá-lo através de um effect adicionaria uma camada de indireção e tornaria o fluxo mais difícil de seguir. Um bom teste: se o código executa por causa de uma interação específica, use um event handler; se executa para manter o componente sincronizado com um sistema externo, use um effect.
Nota de versão
Em class components esse comportamento era dividido entre três lifecycle methods: componentDidMount para setup após a primeira renderização, componentDidUpdate para re-executar quando dados mudavam, e componentWillUnmount para limpeza. Um único useEffect com um array de dependências e uma função de limpeza cobre os três, é por isso que código de setup e teardown relacionado agora vive junto em vez de ser espalhado entre métodos separados.
useEffect como o lugar para código que alcança fora do React: um timer, o título do documento, uma requisição a um servidor. O array no final diz quando executar, [] significando uma vez, logo após o componente aparecer. E se seu effect inicia algo, retorne uma pequena função que o pare. Na maioria das vezes você não precisará de um effect, então é um bom hábito pausar e perguntar se um cálculo simples ou um click handler faria o trabalho primeiro. Próximo: Fetching data, onde componentes puxam dados que vivem inteiramente fora do React.

