Entendendo Hooks Customizados
Um Hook customizado é uma função JavaScript que reutiliza lógica de estado e efeitos do React. Diferente dos componentes, hooks não retornam JSX — eles retornam dados e funções que encapsulam comportamentos específicos. A convenção é nomear hooks com o prefixo use, indicando que seguem as regras dos hooks do React.
Por que isso importa? Quando você percebe que a mesma lógica aparece em múltiplos componentes, é hora de extrair para um hook customizado. Isso elimina duplicação, facilita testes e deixa seus componentes mais legíveis. Um hook bem projetado torna seu código reutilizável sem depender de render props ou HOCs (Higher Order Components).
Criando Seu Primeiro Hook Customizado
Exemplo Prático: useLocalStorage
Vamos criar um hook que sincroniza estado com o localStorage automaticamente. Este é um padrão comum em aplicações reais:
function useLocalStorage(chave, valorInicial) {
const [valor, setValor] = React.useState(() => {
try {
const itemArmazenado = window.localStorage.getItem(chave);
return itemArmazenado ? JSON.parse(itemArmazenado) : valorInicial;
} catch {
return valorInicial;
}
});
const atualizarValor = React.useCallback((novoValor) => {
try {
const valorParaSalvar =
novoValor instanceof Function ? novoValor(valor) : novoValor;
setValor(valorParaSalvar);
window.localStorage.setItem(chave, JSON.stringify(valorParaSalvar));
} catch (erro) {
console.error(`Erro ao salvar ${chave}:`, erro);
}
}, [chave, valor]);
return [valor, atualizarValor];
}
Agora use o hook em qualquer componente:
function MeuComponente() {
const [tema, setTema] = useLocalStorage('tema', 'claro');
return (
<div className={tema}>
<button onClick={() => setTema(tema === 'claro' ? 'escuro' : 'claro')}>
Alternar Tema
</button>
<p>Tema atual: {tema}</p>
</div>
);
}
O hook carrega o valor do localStorage ao iniciar, sincroniza alterações e persiste tudo automaticamente. Sem duplicar essa lógica em cada componente.
Padrões Avançados de Composição
useAsync: Gerenciando Requisições
Requisições HTTP são frequentes e repetitivas. Este hook abstrai toda a complexidade:
function useAsync(funcaoAssincrona, dependencias = []) {
const [estado, setEstado] = React.useState({
status: 'ocioso',
dados: null,
erro: null,
});
React.useEffect(() => {
let montado = true;
setEstado({ status: 'pendente', dados: null, erro: null });
funcaoAssincrona()
.then((dados) => {
if (montado) {
setEstado({ status: 'sucesso', dados, erro: null });
}
})
.catch((erro) => {
if (montado) {
setEstado({ status: 'erro', dados: null, erro });
}
});
return () => {
montado = false;
};
}, dependencias);
return estado;
}
Exemplo de uso:
function ListaUsuarios() {
const { status, dados: usuarios, erro } = useAsync(
() => fetch('/api/usuarios').then(res => res.json()),
[]
);
if (status === 'pendente') return <p>Carregando...</p>;
if (status === 'erro') return <p>Erro: {erro.message}</p>;
if (status === 'sucesso') {
return (
<ul>
{usuarios.map(u => <li key={u.id}>{u.nome}</li>)}
</ul>
);
}
}
Este hook elimina a necessidade de escrever useEffect, tratamento de erros e gerenciamento de estado em cada requisição.
useDebounce: Otimizando Buscas
Buscas em tempo real precisam evitar requisições excessivas. Use debounce:
function useDebounce(valor, atraso = 500) {
const [valorDebounceado, setValorDebounceado] = React.useState(valor);
React.useEffect(() => {
const identificador = setTimeout(() => {
setValorDebounceado(valor);
}, atraso);
return () => clearTimeout(identificador);
}, [valor, atraso]);
return valorDebounceado;
}
Na prática:
function BuscaUsuarios() {
const [busca, setBusca] = React.useState('');
const buscaDebounceada = useDebounce(busca, 300);
const { dados: resultados } = useAsync(
() => fetch(`/api/usuarios?q=${buscaDebounceada}`).then(r => r.json()),
[buscaDebounceada]
);
return (
<>
<input
value={busca}
onChange={(e) => setBusca(e.target.value)}
placeholder="Buscar..."
/>
<ul>
{resultados?.map(u => <li key={u.id}>{u.nome}</li>)}
</ul>
</>
);
}
Boas Práticas e Erros Comuns
O que Fazer
- Nomeie com
use:useFetch,useForm,usePermissoes. A comunidade reconhecerá imediatamente como hooks. - Respeite as regras dos hooks: Chamadas de hooks sempre no escopo raiz da função, nunca dentro de condições ou loops.
- Documente o contrato: Que parâmetros o hook aceita? O que retorna? Quais dependências ele gerencia?
- Teste isoladamente: Hooks podem ser testados independentemente com bibliotecas como
@testing-library/react-hooks.
O que Evitar
❌ Não crie lógica condicional com hooks:
if (condicao) useMeuHook()
✅ Sempre chame hooks incondicionalmente:const resultado = useMeuHook(); if (resultado) {...}❌ Não esqueça dependências em useEffect dentro do hook
✅ Liste todas as variáveis usadas em dependências do useEffect
Hooks customizados que ignoram essas regras causam bugs silenciosos e imprevísiveis. O linter ESLint com plugin react-hooks detecta a maioria desses problemas.
Conclusão
Hooks customizados são a ferramenta mais poderosa do React moderno para reutilização de lógica. Eles substituem padrões antigos como render props e HOCs, deixando o código mais limpo e compreensível. Ao dominar useLocalStorage, useAsync e useDebounce, você tem os padrões fundamentais para criar qualquer abstração necessária em projetos reais.
Lembre-se: toda vez que copiar e colar lógica com useState e useEffect, você é um candidato a criar um novo hook. Isso não apenas elimina duplicação — transforma código complexo em funções simples e testáveis que toda a equipe compreende rapidamente.