Executando verificação de segurança...
2

UseEffect, o hook mais mal utilizado

Porque o UseEffect é o hook mais mal utilizado

Inspiração

Seguindo o Dan Abramov no twitter acabo sabendo sempre que a "nova" (que já está sendo desenvolvida a anos) documentação do react é atualizada com um novo conteúdo, em uma dessas ocasiões resolvi ler o artigo You might not need an effect que fala sobre situações em que desenvolvedores acreditam erroneamente que um useEffect é a solução para um problema e qual a solução que deveriam adotar no lugar

Mal entendido

Não vou aqui listar todas as situações até porque não faria sentido simplesmente reproduzir um artigo oficial que qualquer um pode entrar e ler. Mas um conceito que me chamou atenção e que parece ir de encontro com a maior parte dos mal usos do hook é o de que useEffect é um hook para mudanças de estado que devem acontecer fora do siclo de render
React é um framework bastante versátil, que provê primitivos que te permitem encontrar sua própria solução para um problema. Mas embora ele te permita usar os primitivos de inumeras formas cada um foi criado com um uso em mente, e ao tentar usa-lo de uma forma diferentes a planejada você vai inevitavelmente estar se expondo a problemas que a ferramenta nunca se propôs a evitar

Funcionamento

Se dividirmos um componente em script e template (antes do retorno e depois do retorno) teremos o seguinte funcionamento do ciclo de render após a mudança de um estado:
script -> render -> Effects
Apesar de serem escritos dentro da parte do script os effects são executados apenas após o render, não apenas isso mas quando algum estado é modificado um novo render é forçado.
Mas ao mesmo tempo mudanças de estado acontecem em lote, então mesmo que vários estados sejam modificados apenas um novo render é forçado, mas isso apenas caso sejam modificados no mesmo siclo de render
Então caso você use esteja usando um effect para algo como sincronizar estados, está forçando ao menos 1 re-render a mais que o necessário.
script -> render -> Effects(estado modificado forçando outro ciclo de render) -> script -> render
Essa conta pode subir exponecialmente caso tenha outros effects com dependências de estado

Concerto

Um exemplo de uso do effect para sincronizar estados é por exemplo limpar uma lista caso um filtro seja modificado

const [filter, setFilter] = useState(0)
const [list, setList] = useState([])
useEffect(() => {
  setList([])
}, [filter])  

Para evitar que esse effect force renders desnecessariamente basta trazer o setter para dentro do ciclo de render

const [filter, setFilter] = useState(0)
const [list, setList] = useState([])
const [comparativeFilter, setComparativeFilter] = useState(filter)
if (filter !== comparativeFilter) {
  setList([])
  setComparativeFilter(filter)
}

A mesma lógica se aplica a limitar a quantidade de vezes que um bloco de lógica é executado
Ao invés de fazer

const {setTotalDeComponentesRenderizados} = useGlobalStore()
useEffect(() => {
  setTotalDeComponentesRenderizados((prev) => prev + 1)
}, [])

Podemos simplesmente

const {setTotalDeComponentesRenderizados} = useGlobalStore()
const firstRender = useRef(true)
if (firstRender.current) {
  setTotalDeComponentesRenderizados((prev) => prev + 1)
  firstRender.current = false
}

Outro clássico uso errôneo do effect é para dar foco a inputs usando refs.
A prop de ref em um elemento assim como um effect sem dependências recebe um callback que é executado quando o elemento é montado e executa o retorno desse callback quando desmontado, então quando você passa uma ref para a prop ref

<input
  ref={ref}

Basicamento o que está acontecendo é

<input
  ref={(node) => {
    ref.current = node
  }}

Então se você quer interagir com um elemento quando o componente é montado ao invés de um effect

const inputRef = useRef(null)
useEffect(() => {
  inputRef.current.focus()
}, [])
return (
  <input
    ref={inputRef}

Basta usar uma callbackRef

const focus = useCallback((node) => {
  node.focus()
}, [])
return (
  <input
    ref={focus}

Uso correto

O useEffect tem 2 principais usos:

  1. Ações que não interferem no DOM nem mudam estado (dependendo de estado ou não)
  2. Ações assíncronas que mudam estado ou que precisam de cleanup
    No seguinte exemplo:
const [title, setTitle] = useState("Novo Título")
useEffect(() => {
  window.title = title
}, [title])

Não existe motivo para algo como a mudança de título estar bloqueando o ciclo de render se não vai mudar nada no render em si nem forçar outro render, então o ideal é que seja escrito num effect, já que são executados de forma assíncrona fora do ciclo de render

Cleanup

Muitas pessoas não sabem mas um dos principais recursos do useEffect é que vc pode retornar um callback e ele será executado quando o componente for desmontado
Funções assíncronas correm o risco de serem resolvidas após o componente ter sido desmontado, por isso é importante que o resutado dessas funções sejam impedidos de tentar mudar estados que já não existem

const [posts, setPosts] = useEffect({})
useEffect(() => {
  let componentIsMounted = true
  const getPosts = async () => {
    const res = await fetch(url)
    const data = await res.json()
    if (componentIsMounted) {
      setPosts(data)
    }
  getPosts()
  return () => {
    componentIsMounted = false
}}, [])

O mesmo recurso de cleanup deve ser utilizado para funções cuja execução não é limitada pelo escopo do componente, como eventListners, timeOuts e Intervals

const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
  function updateState() {
    setIsOnline(navigator.onLine);
  }
  updateState();
  window.addEventListener('online', updateState);
  window.addEventListener('offline', updateState);
  return () => {
    window.removeEventListener('online', updateState);
    window.removeEventListener('offline', updateState);
  };
}, []);

Conclusão

Eu ainda pretendo fazer um post só falando disso mas os maiores motivos de toda essa confusão são:

  • O fato de que por o React usar o vdom caso seja necessário interagir com o DOM é preciso escapar do react
  • Os hooks num geral servem para escapar da reatividade, não optar por ela

No fim o conselho que fica é: Não usem React
Sério gente, hoje em dia existem vários frameworks por aí que:

  • Os componentes são executados apenas 1x, optando pela reatividade ao invés de fugir dela
  • São reativos de verdade mudando apenas a parte exata do dom que um estado é referenciado
  • Não dependem de closures pra gerenciar estado
  • Usam o DOM de verdade, permitindo o uso facilmente o uso de métodos do browser

Minha recomendação é o Svelte, que eu considero ser o único framework front-end realmente divertido de se usar
Além de resolver todos os pontos que falei acima ainda:

  • Tem uma sintaxe de declaração e gerênciamento de estado super simples (se quer que um valor seja reativo use let, se que que o valor mude basta atribuir um novo valor a variável)
  • Traz o css pra dentro do componente de forma escopada
  • Facilita o uso de variáveis css baseadas em estado
  • É compilado em javascript puro então não envia dependências em produção, é leve e rápido
  • Tem o melhor meta framework (minha opnião)
  • Dá suporte first class para animações e transições tanto com css como com javascript
  • Consegue usar promises direto no template sem a necessidade de envelopar todo o componente num suspense
  • Te permite optar por 2-way-data-bind podendo referenciar até mesmo Elementos do DOM e Componentes com variáveis
Carregando publicação patrocinada...