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

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
- Define el objetivo: escribe en una frase qué problema resuelve.
- Elige un nombre que empiece por
use(es obligatorio para que React lo reconozca). - Implementa la lógica usando los hooks nativos que necesites (
useState,useEffect,useRef, etc.). - Devuelve los valores que el consumidor necesita, preferiblemente como un objeto.
- 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
useEffectconurlcomo dependencia para volver a lanzar la petición cuando cambie. - El flag
isMountedprotege contrasetStateen 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-hooksoreact‑test‑renderer. Simula diferentes respuestas defetchy 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.