Etapa 4 — Hooks essenciais (useEffect e Custom Hooks)
Nesta etapa você vai aprender dois tópicos fundamentais do React moderno:
- useEffect — como lidar com efeitos colaterais (buscar dados, timers, eventos do DOM, etc).
- Custom Hooks — como criar hooks reutilizáveis para encapsular lógica.
1) O que são Hooks?
Hooks são funções que permitem “ligar” recursos do React (state, ciclo de vida, contexto) em componentes funcionais. Exemplos nativos: useState, useEffect, useRef, useContext, useMemo, useCallback.
Regras importantes (“Rules of Hooks”):
- Chame hooks apenas no topo de componentes React ou custom hooks (não dentro de loops, condições ou funções aninhadas).
- Nome dos custom hooks deve começar com
use(ex.:useFetch).
2) useEffect — o básico
useEffect é o hook para efeitos colaterais: operações que acontecem fora da renderização pura (fetch, timers, subscrições, manipulação do DOM, sincronização com APIs externas).
Sintaxe básica:
useEffect(() => {
// efeito aqui (executa após a renderização)
return () => {
// cleanup (opcional) executado quando o componente desmonta ou antes do próximo efeito
};
}, [dep1, dep2]); // array de dependências
Exemplos simples
- Executar uma vez quando o componente monta:
useEffect(() => {
console.log("Componente montou");
}, []); // array vazio = monta apenas uma vez
- Executar em toda renderização (cuidado!):
useEffect(() => {
console.log("Executa em todo render");
}); // sem array => roda após cada render
- Executar quando uma dependência muda:
useEffect(() => {
document.title = `Você clicou ${count} vezes`;
}, [count]); // roda quando 'count' muda
O array de dependências (por que é importante)
- Liste todas as variáveis externas usadas dentro do efeito que podem mudar (estado, props, funções).
- Se você omitir dependências, pode ter stale closures (efeito usando valores antigos) ou comportamento imprevisível.
- O ESLint (plugin react-hooks) ajuda a detectar dependências faltantes automaticamente.
Função de limpeza (cleanup)
- Retorne uma função dentro do efeito para limpar timers, listeners ou cancelar subscrições.
- Exemplo com
setInterval:
useEffect(() => {
const id = setInterval(() => {
setTime(t => t + 1);
}, 1000);
return () => clearInterval(id); // limpa quando desmonta
}, []);
Exemplos práticos com cleanup
- Event listener (resize):
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
- Abort controller para fetch (evitar setState após desmontar):
useEffect(() => {
const controller = new AbortController();
async function load() {
try {
const res = await fetch(url, { signal: controller.signal });
const json = await res.json();
setData(json);
} catch (err) {
if (err.name !== "AbortError") setError(err);
}
}
load();
return () => controller.abort();
}, [url]);
Erros comuns com useEffect
- Loop infinito: atualizar state dentro do efeito sem controlar dependências.
- Ex.:
useEffect(() => setX(y + 1), [y])pode criar loop se não for planejado.
- Ex.:
- Esquecer o cleanup: causar memory leaks (timers, listeners).
- Não incluir dependências: efeito usa valores antigos (stale values).
- Passar funções não memoizadas como dependência: a função pode mudar em cada render e disparar o efeito desnecessariamente; use
useCallbackquando necessário.
3) useLayoutEffect (breve menção)
- Parecido com
useEffectmas roda síncrono antes do paint do navegador — útil para ler/ajustar layout do DOM antes de pintar. - Na maioria dos casos, use
useEffect; escolhauseLayoutEffectapenas quando precisar ajustar o DOM imediatamente (ex.: medições de tamanho que afetam o layout).
4) Custom Hooks — por que e como criar
O que são? Funções que começam com use e podem chamar outros hooks. Encapsulam lógica reutilizável (fetch, sincronização com localStorage, manipulação de formularios, etc).
Boas práticas
- Nome curto e claro:
useFetch,useLocalStorage,useToggle. - Mantenha o hook focado em uma responsabilidade.
- Hooks podem retornar estado, setters, funções utilitárias ou um objeto com esses valores.
- Coloque-os em
src/hooks/para organização.
Exemplo 1 — useLocalStorage
import { useState, useEffect } from "react";
export function useLocalStorage(key, initialValue) {
const [state, setState] = useState(() => {
const saved = localStorage.getItem(key);
return saved ? JSON.parse(saved) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(state));
}, [key, state]);
return [state, setState];
}
Uso:
const [nome, setNome] = useLocalStorage("nome", "");
Exemplo 2 — useFetch (com loading e cancelamento)
import { useState, useEffect } from "react";
export function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
async function getData() {
try {
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error("Erro HTTP " + res.status);
const json = await res.json();
setData(json);
} catch (err) {
if (err.name !== "AbortError") setError(err);
} finally {
setLoading(false);
}
}
getData();
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
Uso:
const { data, loading, error } = useFetch("https://api.exemplo.com/itens");
Exemplo 3 — useToggle (simples)
import { useState } from "react";
export function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = () => setOn(v => !v);
return [on, toggle];
}
Uso:
const [menuOpen, toggleMenu] = useToggle(false);
Exemplo 4 — useInterval (uso avançado de refs para evitar stale callbacks)
import { useEffect, useRef } from "react";
export function useInterval(callback, delay) {
const savedCallback = useRef();
// atualiza a referência para o callback mais recente
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay === null) return;
function tick() {
savedCallback.current();
}
const id = setInterval(tick, delay);
return () => clearInterval(id);
}, [delay]);
}
Uso:
useInterval(() => {
console.log("tick");
}, 1000);
5) Integração: quando transformar lógica em hook?
- Você tem a mesma lógica repetida em vários componentes.
- Quer separar a lógica (busca, sincronização, validação) da UI.
- Precisa de uma API limpa para controlar comportamento (ex.:
useModalque retorna[open, openModal, closeModal]).
6) Dicas e armadilhas comuns com hooks
- Memoize funções: quando passar funções para dependências ou para componentes filhos, use
useCallbackpara evitar renders/efeitos desnecessários. - useRef para valores mutáveis que não precisam causar re-render (ex.: id de timer, contador interno).
- Evite lógica complexa dentro de effects — extraia em funções ou em hooks para testabilidade.
- Teste hooks com utilitários (React Testing Library + hooks testing utilities).
- Evite efeitos sincronizados pesados no
useLayoutEffect— prefira otimizações de performance e throttle/debounce quando necessário.
7) Exercícios práticos sugeridos
- Implemente
useLocalStoragee use-o para salvar o tema do site (claro/escuro). - Crie
useFetche mostre uma lista de dados carregados de uma API pública. - Faça
useIntervale use para atualizar um relógio na tela. - Converta a lógica de toggle de um modal em
useModal(retorneopen, openModal, closeModal). - Encontre e corrija um bug: um effect que faz
setStatesem dependências e causa loop infinito — explique a causa e solução.
Conclusão
useEffect é a ferramenta padrão para efeitos colaterais e exige atenção às dependências e ao cleanup. Custom hooks permitem organizar e reutilizar lógica de forma elegante — escreva hooks pequenos e bem-focados. Com domínio desses conceitos você avança para arquiteturas melhores (Context, state managers) e componentes mais testáveis e reutilizáveis.