Boas Práticas de Hooks Customizados em React: Abstraindo Lógica Reutilizável para Times Ágeis Já leu

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 , 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: Erro ao salvar ${chave}: Agora use o hook em qualquer componente: O hook carrega o valor do localStorage ao iniciar, sincroniza alterações e persiste tudo automaticamente. Sem

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.

Referências


Artigos relacionados