Crea tus propios hooks personalizados en React: guía práctica

A laptop screen showing a code editor with visible programming code in a dimly lit environment.
Foto de Daniil Komov en Pexels

Los hooks de React nos permiten extraer lógica reutilizable de los componentes y compartirla de forma sencilla. Cuando la lógica se vuelve más compleja o se necesita usarla en varios lugares, crear un hook personalizado es la solución más limpia.

¿Cuándo deberías crear un hook?

  • Necesitas encapsular una pieza de estado o efecto que se repite en varios componentes.
  • Quieres exponer una API sencilla (por ejemplo {data, loading, error}) sin que el consumidor conozca los detalles internos.
  • Deseas probar esa lógica de forma aislada, sin montar un componente completo.

En este artículo veremos cómo diseñar un hook desde cero, siguiendo buenas prácticas de tipado, limpieza y testeo.

Pasos básicos para construir un hook

  1. Define el objetivo: escribe en una frase qué problema resuelve.
  2. Elige un nombre que empiece por use (es obligatorio para que React lo reconozca).
  3. Implementa la lógica usando los hooks nativos que necesites (useState, useEffect, useRef, etc.).
  4. Devuelve los valores que el consumidor necesita, preferiblemente como un objeto.
  5. Gestiona la limpieza de efectos y evita actualizaciones en componentes desmontados.

Ejemplo práctico: useFetch

Imaginemos que queremos un hook que realice peticiones HTTP y nos devuelva data, loading y error. El código queda así:

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(() => {
    let isMounted = true; // evita setState después de desmontar
    setLoading(true);
    fetch(url)
      .then(res => {
        if (!res.ok) throw new Error('Network response was not ok');
        return res.json();
      })
      .then(json => {
        if (isMounted) {
          setData(json);
          setError(null);
        }
      })
      .catch(err => {
        if (isMounted) setError(err);
      })
      .finally(() => {
        if (isMounted) setLoading(false);
      });

    return () => {
      isMounted = false; // cleanup
    };
  }, [url]);

  return { data, loading, error };
}

Algunas notas sobre este fragmento:

  • Usamos useEffect con url como dependencia para volver a lanzar la petición cuando cambie.
  • El flag isMounted protege contra setState en componentes desmontados, evitando warnings.
  • Devolvemos un objeto plano; así el consumidor puede desestructurarlo fácilmente.

Cómo consumir el hook

En cualquier componente basta con importarlo y usarlo como cualquier otro hook:

import React from "react";
import { useFetch } from "./hooks/useFetch";

function UsersList() {
  const { data, loading, error } = useFetch("https://jsonplaceholder.typicode.com/users");

  if (loading) return 

Cargando usuarios…

; if (error) return

Error: {error.message}

; return (
    {data.map(user => (
  • {user.name} ({user.email})
  • ))}
); }

El componente sigue siendo muy legible porque la lógica de la petición está aislada en el hook.

Buenas prácticas que vale la pena seguir

  • Tipado: si usas TypeScript, declara los tipos de los parámetros y del valor de retorno. Así el autocompletado ayuda a quien use el hook.
  • Documentación: comenta qué hace, qué parámetros acepta y qué devuelve. Un bloque JSDoc es suficiente.
  • Testing: prueba el hook con @testing-library/react-hooks o react‑test‑renderer. Simula diferentes respuestas de fetch y verifica los estados.
  • Reusabilidad: evita acoplar el hook a una URL concreta; permite pasar opciones como headers, método o body.
  • Separación de responsabilidades: si la petición necesita transformación de datos, crea otro hook (useTransformData) y compón ambos.

Conclusión

Crear hooks personalizados es una forma elegante de mantener el código DRY y mejorar la legibilidad de tus componentes. Siguiendo los pasos descritos – definir objetivo, nombrar correctamente, gestionar efectos y devolver una API limpia – podrás construir utilidades reutilizables que escalen con tu proyecto. No dudes en añadir pruebas y tipado; la inversión inicial paga dividendos cuando tu base de código crece.