📋 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`
Ejemplo Práctico: useLocalStorage
Este hook personalizado nos permite manejar localStorage de forma reactiva:
💡 Tip: Puedes modificar el código en el editor de la izquierda y ver los cambios en tiempo real.
Top comments (0)