Todos os posts
Reactsábado, 15 de junho de 2024

Domine React Hooks

Uso disciplinado de Hooks em 2024: efeitos como sincronização, concorrência segura, eliminação de estado derivado, custom hooks como API de domínio e controle real de re-render.

Leonardo David

Leonardo David

14 min de leitura

Hooks são poderosos porque tornam composição, estado e efeitos parte do mesmo modelo mental. Eles também são fonte de bugs sutis quando useEffect vira fluxo de controle em vez de sincronização com sistemas externos.

Desde o React 18, com renderização concorrente e Strict Mode executando efeitos duas vezes em desenvolvimento, o uso descuidado de Hooks expõe problemas arquiteturais. O objetivo não é 'fazer funcionar', mas garantir previsibilidade sob concorrência, re-render e transições.

1) Rules of Hooks são invariantes estruturais

Hooks não são funções comuns. A ordem de chamada precisa ser estável entre renders. Chamadas condicionais, dentro de loops ou após retornos antecipados quebram o contrato do reconciler.

// erro estrutural clássico
if (isOpen) {
    useEffect(() => {
      // ❌ inválido
    }, []);
  }

2) useEffect é sincronização com o mundo externo

useEffect não é ferramenta para fluxo de negócio ou derivação de estado. Ele existe para sincronizar com sistemas externos: DOM imperativo, eventos globais, subscriptions, timers, analytics, network side-effects.

Se algo pode ser derivado de props e state, calcule durante o render. Estado derivado em efeito cria inconsistência temporal.

// anti-pattern: estado derivado via efeito
// ❌ Evite
  const [fullName, setFullName] = useState("");
  
  useEffect(() => {
    setFullName(first + " " + last);
  }, [first, last]);
// correto: derivação no render
const fullName = first + " " + last;

3) Efeitos devem ser idempotentes

Em Strict Mode (dev), efeitos executam duas vezes para detectar efeitos não idempotentes. Código que depende de 'rodar uma vez' está incorreto por definição.

// efeito com cleanup adequado
useEffect(() => {
    const controller = new AbortController();
  
    fetch("/api/data", { signal: controller.signal });
  
    return () => {
      controller.abort();
    };
  }, []);

4) Dependências não são sugestão — são contrato

Ignorar eslint-plugin-react-hooks cria closures obsoletas (stale closures). Se um valor é usado dentro do efeito, ele deve estar na lista de dependências ou ser estabilizado.

// stale closure clássico
useEffect(() => {
    const id = setInterval(() => {
      console.log(count); // pode ficar desatualizado
    }, 1000);
  
    return () => clearInterval(id);
  }, []);

5) useMemo e useCallback são otimizações, não arquitetura

Memoização excessiva aumenta complexidade cognitiva. Só use quando: (1) evitar recomputação pesada, (2) manter identidade estável para memoização descendente, (3) evitar rerender caro.

6) Estado local é default. Compartilhamento exige fronteira clara

Elevar estado cedo demais cria acoplamento estrutural. Prefira estado local até haver necessidade real de coordenação entre múltiplas áreas.

7) useRef não é estado invisível

useRef é para armazenar valores mutáveis que não causam re-render. Não use como atalho para evitar arquitetura adequada.

// uso correto de ref para valor mutável
const latestValue = useRef(value);
  
  useEffect(() => {
    latestValue.current = value;
  }, [value]);

8) Custom hooks são APIs de domínio

Um hook deve comunicar intenção de produto. useCheckoutFlow, usePermissionGate, useAutoSaveDraft são contratos semânticos. Isso reduz vazamento de implementação.

// custom hook como boundary de domínio
function useCheckoutFlow() {
    const [step, setStep] = useState<"cart" | "payment" | "confirm">("cart");
  
    const next = () => {
      setStep((s) =>
        s === "cart" ? "payment" :
        s === "payment" ? "confirm" :
        s
      );
    };
  
    return { step, next };
  }

9) Concurrency e transições exigem previsibilidade

Com startTransition e render concorrente, atualizações podem ser interrompidas. Hooks precisam ser determinísticos. Evite efeitos com dependência implícita de ordem temporal.

10) Separação clara: render puro vs side effects

Render deve ser função pura de props + state. Effects conectam o componente ao mundo externo. Misturar ambos gera comportamento difícil de raciocinar sob re-render.

“Os melhores hooks descrevem comportamento de produto com efeitos previsíveis e fronteiras explícitas com o mundo externo.”

— Leonardo David

Checklist de disciplina com Hooks

// verificação arquitetural
const hooksChecklist = [
    "Hook não é chamado condicionalmente?",
    "useEffect sincroniza algo externo?",
    "Não existe estado derivado desnecessário?",
    "Dependências estão completas?",
    "Efeito é idempotente?",
    "Memoização tem justificativa mensurável?",
    "Custom hook expressa domínio e não detalhe técnico?",
  ];

Dominar Hooks não é memorizar APIs. É entender o modelo mental do React: render puro, efeitos previsíveis, identidade estável e concorrência segura. Quando essa disciplina é aplicada, a base do produto se torna mais previsível, testável e evolutiva.

ReactHooksArquiteturaManutenibilidade
Leonardo David

Escrito por Leonardo David

Engenheiro Full Stack. Atuo construindo produtos de alta performance na intersecção entre frontend, backend e estratégia de produto.