📋 Tabla de contenidos
-
useState
– El fundamento del estado -
useEffect
– Efectos secundarios y ciclo de vida -
useContext
– Compartir estado sin props -
useReducer
– Lógica de Estado Compleja y predecible -
useRef
– Referencias, Almacenamiento y Escape -
useMemo
yuseCallback
– Optimizando el Rendimiento - Custom Hooks – Creando Lógica Reutilizable
- Errores Comunes y Soluciones
¿Qué son y por qué son tan potentes?
Los Custom Hooks (Hooks Personalizados) son la caracterÃstica más poderosa de los Hooks. Te permiten extraer y reutilizar lógica con estado de un componente. No son un hook nuevo de React, sino una convención: una función JavaScript que sigue dos reglas:
- Su nombre debe empezar con
use
(ej.useFetch
,useLocalStorage
). - Puede llamar a otros hooks (como
useState
,useEffect
, etc.).
Piensa en ellos como piezas de Lego lógicas. ¿Necesitas saber si el usuario está online? useOnlineStatus
. ¿Quieres interactuar con el Local Storage? useLocalStorage
. ¿Necesitas hacer fetching de datos en varios sitios? useFetch
.
Creando nuestro primer Custom Hook: useFetch
El fetching de datos es un caso de uso perfecto. Casi siempre necesitas manejar el estado de carga, los posibles errores y los datos resultantes. Vamos a encapsular toda esa lógica en un hook reutilizable.
El código del hook useFetch.js
:
// hooks/useFetch.js
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(() => {
// Si la URL no es válida, no hacemos nada.
if (!url) return;
// Usamos AbortController para cancelar el fetch si el componente
// se desmonta o la URL cambia antes de que termine.
const controller = new AbortController();
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) {
throw new Error(`Error: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
// Solo cambiamos loading a false si no fue un aborto
if (!controller.signal.aborted) {
setLoading(false);
}
}
};
fetchData();
// Función de limpieza: aborta el fetch si es necesario.
return () => {
controller.abort();
};
}, [url]); // Se vuelve a ejecutar si la URL cambia
// El hook devuelve un objeto con el estado del fetch
return { data, loading, error };
}
Usando useFetch
en un componente
Ahora, consumir esta lógica compleja se vuelve trivialmente simple.
// components/GitHubUser.js
import React, { useState } from 'react';
import { useFetch } from '../hooks/useFetch';
function GitHubUser() {
const [username, setUsername] = useState('facebook');
const [input, setInput] = useState('facebook');
// ¡Toda la lógica de fetch encapsulada en una sola lÃnea!
const { data, loading, error } = useFetch(`https://api.github.com/users/${username}`);
const handleSubmit = (e) => {
e.preventDefault();
setUsername(input);
}
return (
<div>
<h3>Buscador de Usuarios de GitHub</h3>
<form onSubmit={handleSubmit}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Introduce un usuario de GitHub"
/>
<button type="submit">Buscar</button>
</form>
{loading && <p>Cargando...</p>}
{error && <p>Error: {error}</p>}
{data && (
<div>
<h4>{data.name} (@{data.login})</h4>
<img src={data.avatar_url} alt={data.name} width="100" />
<p>{data.bio}</p>
</div>
)}
</div>
);
}
Como puedes ver, el componente GitHubUser
no sabe nada sobre useState
o useEffect
. Solo le interesa el resultado final: data
, loading
y error
. Esto hace que el código sea increÃblemente limpio, declarativo y fácil de mantener.
✅ Buenas practicas y patrones comunes
- Nombres que empiezan con
use
: Es obligatorio. Esta convención permite a React y a las herramientas de linting saber que tu función es un hook y que debe seguir las reglas de los hooks. - Devuelve un array o un objeto: Si tu hook devuelve múltiples valores, devuélvelos en un objeto
{ valor1, valor2 }
para que el consumo sea más legible. Si es un hook muy genérico y que emula a uno nativo (comouseState
), puedes devolver un array[valor, actualizador]
. - Hazlos genéricos y configurables: Un buen custom hook acepta parámetros para modificar su comportamiento. En
useFetch
, podrÃa aceptar un objeto de opciones para elfetch
. - No rompas las reglas de los hooks: Dentro de tu custom hook, sigue aplicando las mismas reglas: no los llames en bucles, condicionales o funciones anidadas.
🚨 Errores comunes y cómo evitarlos
- Error: Olvidar el prefijo
use
.- Solución: Nombra siempre tus hooks personalizados como
useMiHook
. Si no, React no podrá verificar que estás siguiendo las reglas de los hooks.
- Solución: Nombra siempre tus hooks personalizados como
- Error: Compartir estado entre componentes.
- Aclaración: Es un error conceptual pensar que un custom hook comparte el mismo estado entre diferentes componentes que lo usan. Cada llamada a un custom hook es completamente independiente y tiene su propio estado interno. Si quieres compartir estado, necesitas
useContext
o una librerÃa de estado global.
- Aclaración: Es un error conceptual pensar que un custom hook comparte el mismo estado entre diferentes componentes que lo usan. Cada llamada a un custom hook es completamente independiente y tiene su propio estado interno. Si quieres compartir estado, necesitas
🚀 Retos prácticos
-
useLocalStorage
: Crea un hook que se sincronice conlocalStorage
. Debe funcionar de manera similar auseState
, pero persistiendo el valor en el navegador.
// Uso esperado: const [name, setName] = useLocalStorage('username', 'Invitado');
useOnlineStatus
: Crea un hook que devuelvatrue
si el navegador está conectado a internet yfalse
si no. (Pista:window.addEventListener('online', ...)
ywindow.addEventListener('offline', ...)
).-
useDebounce
: Un hook muy útil. Toma un valor (ej. el texto de un input) y devuelve una versión "retrasada" de ese valor que solo se actualiza después de un cierto tiempo sin cambios. Es perfecto para evitar hacer peticiones a una API en cada pulsación de tecla.
// Uso esperado: const [searchTerm, setSearchTerm] = useState(''); const debouncedSearchTerm = useDebounce(searchTerm, 500); // 500ms de retraso // Usa `debouncedSearchTerm` en tu `useFetch`
Top comments (0)