Dominando Hooks em React: useState, useEffect, useRef e useCallback em Projetos Reais Já leu

useState: Gerenciando Estado em Componentes Funcionais O é o hook fundamental do React que permite adicionar estado a componentes funcionais. Antes dos hooks, apenas componentes de classe podiam ter estado. Ele retorna um array com dois elementos: o valor atual do estado e uma função para atualizá-lo. Um detalhe crucial: quando você chama , o React não atualiza o estado imediatamente no síncrono. Ele marca o componente para re-renderização e agenda a atualização. Se você precisar do novo valor imediatamente, use a versão funcional: . Esta abordagem é especialmente importante em loops ou quando múltiplas atualizações ocorrem em sequência. useEffect: Efeitos Colaterais e Ciclo de Vida O executa código após a renderização. Ele substitui os métodos de ciclo de vida , e de componentes de classe. O segundo argumento é a dependency array — um array que controla quando o efeito roda novamente. https://jsonplaceholder.typicode.com/users/${id} As dependency arrays funcionam assim: sem array = executa após cada render; array vazio = executa

useState: Gerenciando Estado em Componentes Funcionais

O useState é o hook fundamental do React que permite adicionar estado a componentes funcionais. Antes dos hooks, apenas componentes de classe podiam ter estado. Ele retorna um array com dois elementos: o valor atual do estado e uma função para atualizá-lo.

import { useState } from 'react';

function Contador() {
  const [count, setCount] = useState(0);
  const [nome, setNome] = useState('');

  return (
    <div>
      <p>Contagem: {count}</p>
      <button onClick={() => setCount(count + 1)}>Incrementar</button>

      <input 
        value={nome} 
        onChange={(e) => setNome(e.target.value)} 
        placeholder="Digite seu nome"
      />
      <p>Olá, {nome}!</p>
    </div>
  );
}

Um detalhe crucial: quando você chama setCount(count + 1), o React não atualiza o estado imediatamente no síncrono. Ele marca o componente para re-renderização e agenda a atualização. Se você precisar do novo valor imediatamente, use a versão funcional: setCount(prevCount => prevCount + 1). Esta abordagem é especialmente importante em loops ou quando múltiplas atualizações ocorrem em sequência.

useEffect: Efeitos Colaterais e Ciclo de Vida

O useEffect executa código após a renderização. Ele substitui os métodos de ciclo de vida componentDidMount, componentDidUpdate e componentWillUnmount de componentes de classe. O segundo argumento é a dependency array — um array que controla quando o efeito roda novamente.

import { useState, useEffect } from 'react';

function Usuario({ id }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);

    fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
      .then(res => res.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      })
      .catch(err => {
        console.error(err);
        setLoading(false);
      });

    // Cleanup function (desmontagem ou antes do próximo efeito)
    return () => {
      console.log('Limpando recursos para o usuário:', id);
    };
  }, [id]); // Rodar novamente apenas quando 'id' mudar

  if (loading) return <p>Carregando...</p>;
  if (!user) return <p>Usuário não encontrado</p>;

  return <h1>{user.name}</h1>;
}

As dependency arrays funcionam assim: sem array = executa após cada render; array vazio = executa apenas uma vez (montagem); array com valores = executa quando esses valores mudam. A função retornada é o cleanup, chamada antes do efeito rodar novamente ou quando o componente desmonta. Use para cancelar requisições, remover listeners ou liberar memória.

useRef: Acessando Elementos DOM e Armazenando Valores Mutáveis

O useRef cria uma referência persistente que não causa re-renderização ao ser alterada. Diferente do useState, mudar um ref não dispara atualização do componente. Use para acessar diretamente elementos do DOM ou para armazenar valores que precisam persistir entre renders sem afetar a lógica de renderização.

import { useRef, useState } from 'react';

function FormularioComFoco() {
  const inputRef = useRef(null);
  const renderCountRef = useRef(0);
  const [nome, setNome] = useState('');

  const focarNoInput = () => {
    inputRef.current.focus();
  };

  const handleChange = () => {
    renderCountRef.current += 1;
    console.log(`Render #${renderCountRef.current}`);
  };

  return (
    <div>
      <input 
        ref={inputRef} 
        value={nome}
        onChange={(e) => {
          setNome(e.target.value);
          handleChange();
        }}
        placeholder="Digite algo"
      />
      <button onClick={focarNoInput}>Focar no Input</button>
      <p>O input foi alterado {renderCountRef.current} vezes</p>
    </div>
  );
}

Cuidado: evite usar refs para tudo. Se você quer contar renders, use useState. Refs são para casos específicos: controlar foco, disparar animações, integrar com bibliotecas de terceiros que manipulam o DOM diretamente, ou armazenar valores que não afetam a renderização.

useCallback: Otimizando Funções e Evitando Re-renderizações Desnecessárias

O useCallback memoriza uma função, retornando a mesma referência entre renders enquanto suas dependências não mudarem. Isso é crítico quando você passa callbacks como props para componentes filhos otimizados com React.memo, pois evita re-renderizações desnecessárias.

import { useState, useCallback } from 'react';

function ListaTarefas() {
  const [tarefas, setTarefas] = useState(['Estudar', 'Exercitar']);
  const [input, setInput] = useState('');

  // Sem useCallback, uma nova função é criada a cada render
  // Se PassarParaFilho estiver em React.memo, vai re-renderizar sempre
  const adicionarTarefa = useCallback(() => {
    if (input.trim()) {
      setTarefas([...tarefas, input]);
      setInput('');
    }
  }, [input, tarefas]); // Recriar quando input ou tarefas mudam

  return (
    <div>
      <input 
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="Nova tarefa"
      />
      <BotaoAdicionar onClick={adicionarTarefa} />
      <ul>
        {tarefas.map((t, i) => <li key={i}>{t}</li>)}
      </ul>
    </div>
  );
}

const BotaoAdicionar = React.memo(({ onClick }) => {
  console.log('BotaoAdicionar renderizado');
  return <button onClick={onClick}>Adicionar</button>;
});

Não otimize prematuramente. Use useCallback quando: (1) a função é passada como prop para componentes otimizados com React.memo, ou (2) a função é dependência de outros hooks como useEffect e useCallback. Se usá-lo sem necessidade, apenas adiciona complexidade.

Conclusão

useState permite que componentes funcionais tenham estado reativo e re-renderizem quando mudam. useEffect executa efeitos colaterais em momentos específicos do ciclo de vida, com cleanup automático. useRef fornece acesso mutável a valores e elementos DOM sem disparar re-renderizações. useCallback memoriza funções para otimizar performance em cenários específicos. Domine esses quatro e você terá 80% do que precisa para React moderno. A chave é entender quando usar cada um, não apenas como.

Referências


Artigos relacionados