Sobre

Os Hooks personalizados no React são uma forma poderosa de extrair lógica de componentes e torná-la reutilizável. Eles permitem compartilhar lógica de estado e efeitos colaterais entre múltiplos componentes sem recorrer a componentes de alta ordem ou render props.

Aqui, vou criar um Hook personalizado chamado useFetch, que encapsula a lógica para realizar chamadas de API HTTP. Este Hook pode ser usado em qualquer componente que precise buscar dados de uma API.

import { useState, useEffect } from 'react';
 
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
 
  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        setData(data);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoading(false);
      }
    };
 
    fetchData();
 
    // Cleanup function
    return () => {
      setData(null); // Optional: clear data when the component using the hook unmounts
    };
  }, [url]); // Dependências: o Hook será re-executado se o URL mudar
 
  return { data, loading, error };
}
 
export default useFetch;

Exemplo de uso:

import React from 'react';
import useFetch from './hooks/useFetch';
 
function UserList() {
  const { data: users, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');
 
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return (
    <div>
      <h1>Users</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name} - {user.email}</li>
        ))}
      </ul>
    </div>
  );
}
 
export default UserList;

Como funciona:

  • useState: Três estados são gerenciados dentro do Hook — data, loading, e error. Isso permite que o Hook gerencie o ciclo de vida completo de uma solicitação HTTP.
  • useEffect: Este Hook é usado para realizar a solicitação HTTP quando o componente é montado ou quando o url passado como argumento muda. Ele também inclui uma função de limpeza que pode ser usada para realizar qualquer limpeza necessária.
  • fetchData: Esta função assíncrona faz a solicitação HTTP usando a API fetch. Ela atualiza o estado com os dados, um possível erro, e o estado de carregamento.

Quando utilizar um hook personalizado?

A decisão de criar um Hook personalizado no React geralmente surge quando você identifica uma lógica que é comum a vários componentes ou que tornaria um componente único muito complexo. Criar um Hook personalizado permite extrair essa lógica para um local reutilizável, mantendo seus componentes mais limpos, mais fáceis de entender e de manter. Aqui estão algumas situações específicas onde você pode considerar criar um Hook personalizado:

  1. Reutilização de Lógica de Estado e Efeitos Colaterais: Se você tem vários componentes que compartilham a mesma lógica complexa de estado ou efeitos colaterais (como chamadas de API, subscrições a eventos, timers), um Hook personalizado pode encapsular essa lógica e ser compartilhado entre esses componentes.

  2. Complexidade Reduzida em Componentes: Se um componente está ficando muito complexo devido à múltipla lógica de estado e efeitos colaterais, você pode refatorar parte dessa lógica para um Hook personalizado. Isso mantém seu componente focado na renderização da UI e delega a gestão de comportamento específico para o Hook.

  3. Separação de Preocupações: Hooks personalizados ajudam a seguir o princípio da separação de preocupações. Eles permitem que você separe a lógica de negócios da lógica de UI, o que facilita o teste e a manutenção do código.

  4. Lógica de Formulários Complexos: Em formulários complexos que envolvem validações, gerenciamento de múltiplos campos e seus estados, e manipulação de envio, um Hook personalizado pode simplificar muito a gestão do formulário.

  5. Interação com APIs Externas e WebSockets: Quando seu componente precisa interagir com APIs externas, WebSockets ou qualquer outro tipo de serviço externo, encapsular essa lógica em um Hook personalizado pode tornar seu código mais limpo e fácil de usar em outros componentes.

  6. Melhorar a Testabilidade: Testar componentes que contêm lógica de estado e efeitos colaterais misturados com UI pode ser complicado. Extrair essa lógica para Hooks personalizados pode facilitar muito a escrita de testes isolados para essa lógica.

Exemplo Prático

Vamos supor que você tenha um sistema onde vários componentes precisam acessar e manipular dados do usuário. Em vez de repetir a lógica de obtenção, atualização e gestão de erro em cada componente, você pode criar um Hook personalizado:

function useUserData(userId) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
 
  useEffect(() => {
    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then(response => response.json())
      .then(setUser)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [userId]);
 
  return { user, loading, error };
}

Este Hook pode ser utilizado por qualquer componente que necessite dos dados do usuário, mantendo a lógica de fetch centralizada e facilitando a manutenção.