O que são React Hooks?

Antes do React 16.8 (lançado em 2019), toda vez que você precisava de estado ou de ciclo de vida em um componente, era obrigatório escrever uma classe. O código ficava verboso, difícil de reutilizar e complicado de testar.

Os Hooks chegaram para resolver esse problema. São funções especiais que permitem que você "conecte" (hook into) funcionalidades do React diretamente dentro de componentes funcionais.

Regra fundamental dos Hooks

Hooks só podem ser chamados no nível raiz de um componente funcional ou dentro de outro Hook customizado. Nunca dentro de loops, condições ou funções aninhadas.

Os Hooks mais importantes do React são: useState, useEffect, useContext, useReducer, useRef, useMemo e useCallback. Neste guia vamos focar nos três primeiros e depois aprender a criar os nossos próprios.

useState — Gerenciando Estado

O useState é o Hook mais básico e mais usado. Ele permite que você adicione uma variável de estado ao seu componente. A cada mudança nessa variável, o componente é re-renderizado automaticamente pelo React.

A sintaxe é simples:

import { useState } from 'react';

function Contador() {
  const [contagem, setContagem] = useState(0); // valor inicial = 0

  return (
    <div>
      <p>Você clicou {contagem} vezes</p>
      <button onClick={() => setContagem(contagem + 1)}>
        Clique aqui
      </button>
    </div>
  );
}

useState retorna um array com dois elementos: o valor atual do estado e uma função para atualizá-lo. Usamos desestruturação para nomeá-los como quisermos.

Exemplos práticos

O estado pode armazenar qualquer tipo de valor: números, strings, booleanos, objetos e arrays. Veja um exemplo com um formulário simples:

function FormularioLogin() {
  const [email, setEmail]     = useState('');
  const [senha, setSenha]     = useState('');
  const [carregando, setCarregando] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setCarregando(true);
    // chamada à API...
    setCarregando(false);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="E-mail"
      />
      <input
        type="password"
        value={senha}
        onChange={(e) => setSenha(e.target.value)}
        placeholder="Senha"
      />
      <button type="submit" disabled={carregando}>
        {carregando ? 'Entrando...' : 'Entrar'}
      </button>
    </form>
  );
}
Dica

Ao atualizar um estado que depende do valor anterior, use a forma funcional: setContagem(prev => prev + 1). Isso garante que você sempre esteja usando o valor mais recente, mesmo em atualizações assíncronas.

useEffect — Efeitos Colaterais

O useEffect permite que você execute código depois que o componente é renderizado. É usado para: buscar dados de uma API, manipular o DOM, configurar timers, inscrever-se em eventos e muito mais.

import { useState, useEffect } from 'react';

function ListaUsuarios() {
  const [usuarios, setUsuarios] = useState([]);
  const [carregando, setCarregando] = useState(true);

  useEffect(() => {
    // Executa após a renderização
    fetch('https://api.exemplo.com/usuarios')
      .then(res => res.json())
      .then(dados => {
        setUsuarios(dados);
        setCarregando(false);
      });
  }, []); // Array de dependências vazio = executa apenas uma vez

  if (carregando) return <p>Carregando...</p>;

  return (
    <ul>
      {usuarios.map(u => (
        <li key={u.id}>{u.nome}</li>
      ))}
    </ul>
  );
}

O segundo argumento do useEffect é o array de dependências:

  • Array vazio [] — executa uma única vez (equivalente ao componentDidMount).
  • Array com variáveis — executa sempre que alguma delas mudar.
  • Sem array — executa após toda renderização (evite!).

Cleanup e dependências

Quando seu efeito cria recursos que precisam ser liberados (listeners, timers, conexões WebSocket), retorne uma função de limpeza:

useEffect(() => {
  const timer = setInterval(() => {
    console.log('tick');
  }, 1000);

  // Função de cleanup — executada quando o componente é desmontado
  return () => clearInterval(timer);
}, []);
Cuidado com o loop infinito

Se você atualizar um estado dentro de um useEffect sem especificar dependências, causará um loop infinito. Sempre defina o array de dependências corretamente.

useContext — Compartilhando Estado Global

Imagine que você tem um componente de layout que precisa saber se o usuário está logado. Sem o Context API, você precisaria passar essa informação via props por cada nível da árvore de componentes — o famoso "prop drilling".

O useContext resolve isso elegantemente:

import { createContext, useContext, useState } from 'react';

// 1. Criar o contexto
const AuthContext = createContext(null);

// 2. Criar o Provider (envolve sua aplicação)
export function AuthProvider({ children }) {
  const [usuario, setUsuario] = useState(null);

  const login  = (dados) => setUsuario(dados);
  const logout = ()      => setUsuario(null);

  return (
    <AuthContext.Provider value={{ usuario, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// 3. Consumir em qualquer componente filho
function Navbar() {
  const { usuario, logout } = useContext(AuthContext);

  return (
    <nav>
      {usuario ? (
        <>
          <span>Olá, {usuario.nome}!</span>
          <button onClick={logout}>Sair</button>
        </>
      ) : (
        <a href="/login">Entrar</a>
      )}
    </nav>
  );
}

Custom Hooks — Reutilizando Lógica

Um dos maiores poderes dos Hooks é a capacidade de criar os seus próprios. Um Custom Hook é simplesmente uma função JavaScript cujo nome começa com use e que pode chamar outros Hooks internamente.

Imagine que você busca dados de API em vários lugares da aplicação. Ao invés de repetir a mesma lógica, extraia para um Hook:

import { useState, useEffect } from 'react';

// Custom Hook: useFetch
function useFetch(url) {
  const [dados, setDados]         = useState(null);
  const [carregando, setCarregando] = useState(true);
  const [erro, setErro]           = useState(null);

  useEffect(() => {
    setCarregando(true);
    setErro(null);

    fetch(url)
      .then(res => {
        if (!res.ok) throw new Error(`Erro ${res.status}`);
        return res.json();
      })
      .then(dados => {
        setDados(dados);
        setCarregando(false);
      })
      .catch(err => {
        setErro(err.message);
        setCarregando(false);
      });
  }, [url]);

  return { dados, carregando, erro };
}

// Uso em qualquer componente
function Produtos() {
  const { dados, carregando, erro } = useFetch('/api/produtos');

  if (carregando) return <p>Carregando produtos...</p>;
  if (erro)       return <p>Erro: {erro}</p>;

  return (
    <ul>
      {dados.map(p => <li key={p.id}>{p.nome}</li>)}
    </ul>
  );
}

Perceba como o componente Produtos ficou limpo e legível. Toda a complexidade da busca de dados está encapsulada no Custom Hook, que pode ser reutilizado em qualquer componente da aplicação.

Boas Práticas

  • Nomeie bem o estado: prefira const [isLoading, setIsLoading] a const [flag, setFlag].
  • Um estado por responsabilidade: não agrupe estados não relacionados em um único objeto.
  • Extraia Custom Hooks: se um componente tem mais de 2-3 efeitos ou lógica complexa, é hora de extrair.
  • Use o ESLint plugin: instale eslint-plugin-react-hooks para detectar problemas de dependências automaticamente.
  • Evite re-renders desnecessários: use useMemo e useCallback para funções e valores computados caros.
  • Prefira useReducer: quando o estado tem múltiplas sub-variáveis ou transições complexas, useReducer deixa o código mais organizado que vários useState.

Conclusão

Os React Hooks transformaram completamente a forma como escrevemos componentes React. Com useState gerenciamos estado local de forma simples, com useEffect coordenamos efeitos colaterais e ciclo de vida, e com useContext compartilhamos dados globais sem prop drilling.

Mas o maior diferencial está nos Custom Hooks: eles nos permitem extrair e reutilizar lógica de forma elegante, tornando nossos componentes menores, mais legíveis e mais fáceis de testar.

"Bons componentes React fazem uma coisa só. Custom Hooks fazem isso possível sem sacrificar funcionalidades."

No próximo artigo vamos explorar useReducer e useMemo para casos mais avançados. Fique ligado!