React: quando (e como) criar um custom hook
Entenda o que realmente é um custom hook no React, o sinal de que um pedaço de lógica merece virar um, e um exemplo prático extraindo lógica repetida de dois componentes pra um hook reutilizável.
Já falei aqui sobre Activity, sobre reducer e sobre Server Components, mas ficou faltando um assunto mais básico do dia a dia com React: custom hook. É um dos recursos mais usados pelo ecossistema inteiro (todo hook de biblioteca que você importa, tipo useForm ou useQuery, é um custom hook por baixo), mas pouca gente para pra entender o que de fato define um.
Custom hook não é mágica, é só uma função
Um custom hook é uma função JavaScript comum, com uma única regra: o nome começa com use. Essa convenção existe porque o React usa ela pra saber que aquela função pode chamar outros hooks (useState, useEffect) dentro dela, e aplicar as regras de hooks nesse contexto.
function useContador() {
const [contador, setContador] = useState(0);
function incrementar() {
setContador((c) => c + 1);
}
return { contador, incrementar };
}Isso já é um custom hook, mesmo sendo simples. Qualquer componente pode chamar useContador() e ganhar um estado de contador com a função de incrementar pronta, sem duplicar essa lógica.
O sinal de que vale a pena extrair um hook
O gatilho mais claro pra criar um custom hook é perceber a mesma lógica de estado se repetindo em componentes diferentes. Imagina dois componentes que, cada um do seu jeito, buscam dado de uma API e controlam loading e erro:
// componente 1
function ListaUsuarios() {
const [dados, setDados] = useState(null);
const [carregando, setCarregando] = useState(true);
const [erro, setErro] = useState(null);
useEffect(() => {
fetch("/api/usuarios")
.then((res) => res.json())
.then(setDados)
.catch(setErro)
.finally(() => setCarregando(false));
}, []);
// ...
}Se o componente ListaProdutos faz exatamente a mesma dança, só trocando a URL, isso é o sinal de que essa lógica não é sobre "lista de usuários", é sobre "buscar dado com controle de loading e erro", um conceito genérico o suficiente pra virar hook:
function useFetch(url) {
const [dados, setDados] = useState(null);
const [carregando, setCarregando] = useState(true);
const [erro, setErro] = useState(null);
useEffect(() => {
setCarregando(true);
fetch(url)
.then((res) => res.json())
.then(setDados)
.catch(setErro)
.finally(() => setCarregando(false));
}, [url]);
return { dados, carregando, erro };
}E os dois componentes ficam só com a parte que realmente é específica deles:
function ListaUsuarios() {
const { dados, carregando, erro } = useFetch("/api/usuarios");
// ...
}
function ListaProdutos() {
const { dados, carregando, erro } = useFetch("/api/produtos");
// ...
}Hook não precisa lidar só com estado
Um custom hook também é o lugar certo pra esconder efeito colateral que, espalhado direto no componente, deixaria o código mais difícil de ler. Um exemplo comum é escutar o tamanho da tela:
function useLargura() {
const [largura, setLargura] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setLargura(window.innerWidth);
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return largura;
}O componente que usa isso não precisa saber que existe um addEventListener por trás, nem se preocupar em limpar o listener quando desmontar. Ele só chama const largura = useLargura() e segue com a própria lógica, o hook resolve o efeito colateral escondido.
Quando não vale a pena extrair
Nem toda função com useState dentro precisa virar hook separado. Se a lógica só existe num componente e não tem sinal nenhum de que vai se repetir em outro lugar, extrair cedo demais só adiciona uma camada de indireção sem ganho real: agora, pra entender o componente, é preciso pular pra outro arquivo e voltar. Vale extrair quando a repetição já apareceu de verdade, ou quando a lógica é complexa o suficiente pra atrapalhar a leitura do componente mesmo sozinha.
Fechando
Custom hook é, na essência, extrair lógica com estado ou efeito colateral pra fora do componente, do mesmo jeito que você extrairia uma função comum pra evitar repetir código. A diferença é só a convenção do use na frente, que avisa o React (e quem lê o código depois) que ali dentro tem outros hooks sendo usados. Não é sobre parecer mais avançado usando hook customizado, é sobre manter componente enxuto, focado só no que ele realmente precisa mostrar.
Leia também