Skip to content
This page has been auto-translated and may contain errors.View in English

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:

jsx
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:

jsx
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:

jsx
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:

jsx
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.

A pergunta prática com qualquer effect é o que pertence ao array de dependências. A resposta é mecânica: todo valor reativo que o effect lê, significando toda prop, variável de state ou valor derivado delas que aparece dentro da função do effect, pertence ao array. Omita um e o effect continua usando uma cópia desatualizada dele em vez de captar as mudanças.

jsx
function SearchResults({ query }) {
  const [results, setResults] = useState([])

  useEffect(() => {
    let active = true
    fetch(`/api/search?q=${query}`)
      .then(r => r.json())
      .then(data => { if (active) setResults(data) })
    return () => { active = false }
  }, [query])

  return <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>
}

Este effect lê query, então query está no array. Nada mais no escopo muda o que o effect faz, então nada mais precisa estar lá. Você não precisa trabalhar isso de memória. A regra de lint exhaustive-deps do eslint-plugin-react-hooks lê a função do effect e sinaliza qualquer valor que ela usa e que está faltando no array. Trate seus avisos como bugs reais para corrigir, não como ruído para silenciar, pois um aviso suprimido aqui é exatamente como bugs de valor desatualizado escapam para a produção.

Quando um effect começa a fazer dois trabalhos não relacionados, divida-o em dois effects em vez de um effect com uma lista de dependências mais longa. Um componente que tanto busca dados de um usuário quanto define o título do documento baseado naquele usuário é mais fácil de compreender como duas chamadas useEffect separadas, cada uma com seu próprio array de dependências focado, do que como um effect malabarista com ambas as preocupações. Cada effect deve sincronizar uma coisa com o mundo externo.

Um teste rápido para saber se código pertence a um effect: ele busca algo, inscreve-se em algo ou alcança e toca o DOM ou algum outro sistema fora do React diretamente? Esse é território de effect. Ele calcula um valor a partir de props ou state que você já tem? Isso pertence ao corpo da renderização, não a um effect. Recorrer a useEffect para derivar um valor é a forma mais comum de effects serem usados demais.

O timing de effects vale a pena ser preciso. Seu effect não executa durante a renderização. React renderiza o componente, confirma a atualização do DOM, o navegador pinta, e só então o effect executa. Isso é deliberado: o effect vê uma tela que já reflete a renderização atual, e nunca bloqueia o navegador de pintar. Isso também significa que você não pode contar com um effect para produzir algo que o usuário vê antes da primeira pintura. Para o caso mais raro em que você precisa medir ou mutar o DOM antes da pintura, existe useLayoutEffect, que executa sincronamente após a confirmação e antes do navegador pintar.

Você notará em desenvolvimento que effects executam duas vezes no mount. O Strict Mode do React intencionalmente monta cada componente, desmonta e monta novamente. Ele executa seu effect, executa a limpeza, depois executa o effect uma segunda vez. Isso é um teste de stress para limpeza. Se seu effect configura algo mas nunca o desfaz, a execução dupla superficializa o bug imediatamente, porque você verá dois intervalos ou duas subscriptions em vez de um. Isso acontece apenas em desenvolvimento. Em produção o effect executa uma vez.

Duas armadilhas do array de dependências valem a pena internalizar. A primeira é closures desatualizados. Um effect captura os valores da renderização em que foi criado. Se você omite um valor do array de dependências, o effect continua lendo o valor antigo daquela renderização mesmo depois que ele mudou, e nunca re-executa para captar o novo. O corretivo é listar todo valor que o effect lê, ou usar a forma updater como setCount(c => c + 1) para você não precisar ler o valor atual de jeito nenhum. A segunda é identidade. Objetos, arrays e funções criados durante a renderização são valores frescos a cada vez, então listar um como dependência faz o effect re-executar a cada renderização, já que a referência nunca é igual à da última vez. Quando você precisa de um como dependência, defina-o dentro do effect, ou envolva-o em useMemo ou useCallback (hooks que armazenam um valor ou função entre renderizações) para sua identidade permanecer estável entre renderizações.

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.

JunoEffects sincronizam com o mundo externo Pense em 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.
JunoEffects sincronizam com o mundo externouseEffect executa após a renderização para sincronizar com algo que React não possui. O array de dependências é o controle: [] executa uma vez, [a, b] re-executa quando esses mudam, e nenhum array executa a cada renderização. Retorne uma função de limpeza para qualquer coisa que você inicia, inscreve-se ou abre. Antes de escrever um, verifique se o valor pode ser derivado durante a renderização ou se o trabalho pertence a um event handler, porque esses cobrem uma quantidade surpreendente do que parece ser território de effect.
JunoEffects sincronizam com o mundo externo Effects executam após a confirmação, então veem uma tela pintada e nunca a bloqueiam; recorra a useLayoutEffect apenas quando deve tocar o DOM antes da pintura. O duplo mount do Strict Mode em desenvolvimento é um teste de limpeza, então trate qualquer intervalo ou subscription duplicado como um bug real. As duas armadilhas de dependências são closures desatualizados de valores omitidos e identidades instáveis de objetos ou funções que re-executam o effect a cada renderização; a forma updater, useMemo e useCallback são como você as desarma.

Próximo: Fetching data, onde componentes puxam dados que vivem inteiramente fora do React.