📋 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é es y para qué sirve?
useEffect
conecta tus componentes con sistemas externos. Permite ejecutar "efectos secundarios" después de que el componente se haya renderizado. Un efecto secundario es cualquier código que afecta a algo fuera del propio componente, como:
- Llamadas a una API para buscar datos.
- Suscripciones a eventos (ej.
window.addEventListener
). - Manipulación manual del DOM.
- Timers como
setInterval
osetTimeout
.
useEffect
reemplaza a los métodos de ciclo de vida componentDidMount
, componentDidUpdate
y componentWillUnmount
de los componentes de clase.
Sintaxis y el array de dependencias
useEffect(() => {
// ...tu código del efecto...
return () => {
// ...función de limpieza (opcional)...
};
}, [dependencias]);
La parte más importante de useEffect
es el array de dependencias. Controla cuándo se ejecuta el efecto:
-
[dependencia1, dependencia2]
: El efecto se ejecuta solo si alguna de las dependencias ha cambiado desde el último render. Este es el caso más común. -
[]
(array vacío): El efecto se ejecuta una sola vez, justo después del renderizado inicial (equivalente acomponentDidMount
). Perfecto para inicializaciones o fetching de datos que no cambian. - No se proporciona el array: El efecto se ejecuta después de cada renderizado. ¡Cuidado! Esto puede causar bucles infinitos si el efecto actualiza el estado.
La función de limpieza (return
) es opcional y se ejecuta:
- Antes de que el efecto se vuelva a ejecutar (si una dependencia cambió).
- Cuando el componente se desmonta (se elimina de la pantalla).
Es crucial para limpiar suscripciones, timers o listeners y evitar fugas de memoria.
Ejemplo práctico detallado: fetching de datos con estado de carga y error
Este es el caso de uso más común para useEffect
. Vamos a buscar datos de un Pokémon y a manejar los estados intermedios.
import React, { useState, useEffect } from 'react';
function PokemonInfo({ pokemonName }) {
const [pokemon, setPokemon] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!pokemonName) return;
// Reseteamos los estados al empezar una nueva búsqueda
setLoading(true);
setError(null);
setPokemon(null);
fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonName}`)
.then(response => {
if (!response.ok) {
throw new Error('No se encontró el Pokémon');
}
return response.json();
})
.then(data => {
setPokemon(data);
})
.catch(error => {
setError(error.message);
})
.finally(() => {
setLoading(false);
});
}, [pokemonName]); // El efecto depende del nombre del Pokémon
if (loading) return <p>Cargando...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
{pokemon && (
<>
<h3>{pokemon.name}</h3>
<img src={pokemon.sprites.front_default} alt={pokemon.name} />
</>
)}
</div>
);
}
✅ Buenas practicas y patrones comunes
- Un efecto, una responsabilidad: Si tienes lógicas no relacionadas, sepáralas en varios
useEffect
. Uno para fetching, otro para unevent listener
, etc. Esto hace el código más limpio. - Limpia siempre tus efectos: Si te suscribes a algo, crea un timer o un listener, siempre devuelve una función de limpieza para cancelarlo.
- No olvides las dependencias: El linter de React te suele avisar si usas una variable o función dentro del efecto pero no la incluyes en el array. ¡No lo ignores! Omitir dependencias puede causar bugs muy difíciles de rastrear.
- Funciones en el array de dependencias: Si una función se usa dentro de
useEffect
, debe estar en el array. Pero si esa función se redefine en cada render, causará que el efecto se ejecute siempre. La solución es definir la función dentro deluseEffect
o memorizarla conuseCallback
.
🚨 Errores comunes y cómo evitarlos
-
Error: El bucle infinito.
// MAL ❌ const [count, setCount] = useState(0); useEffect(() => { // Este efecto cambia el estado, lo que causa un re-render, // lo que vuelve a ejecutar el efecto, y así hasta el infinito. setCount(count + 1); }); // Sin array de dependencias
- Solución: Añade un array de dependencias. Si quieres que se ejecute solo una vez, que sea
[]
. Si quieres que reaccione a un cambio, incluye la dependencia[otraVariable]
.
- Solución: Añade un array de dependencias. Si quieres que se ejecute solo una vez, que sea
-
Error: Datos "viejos" (stale state) por dependencias faltantes.
// MAL ❌ const [count, setCount] = useState(0); useEffect(() => { // Este intervalo siempre "verá" count como 0, porque la función // del intervalo se creó en el primer render y nunca se actualizó. const intervalId = setInterval(() => { console.log(`El contador es: ${count}`); }, 1000); return () => clearInterval(intervalId); }, []); // El array vacío le dice a React: "nunca vuelvas a ejecutar esto"
-
Solución 1: Añadir la dependencia.
// BIEN ✅ (pero recrea el intervalo cada vez que count cambia) useEffect(() => { ... }, [count]);
-
Solución 2: Usar la actualización funcional (mejor en este caso).
// EXCELENTE ✅ // Dentro de `setCount`, React siempre te da el valor más reciente. // No necesitamos `count` como dependencia. useEffect(() => { const intervalId = setInterval(() => { setCount(c => c + 1); // usamos la versión más reciente del estado }, 1000); return () => clearInterval(intervalId); }, []);
-
🚀 Retos prácticos
- Temporizador con Start/Stop/Reset: Usa
useEffect
yuseState
para crear un cronómetro funcional. - Seguir el ratón: Crea un componente que muestre las coordenadas X e Y del ratón en tiempo real. (Pista:
window.addEventListener('mousemove', ...)
, y no olvides limpiar el listener). - Título de la página dinámico: Haz que el título del documento (
document.title
) se actualice para mostrar el valor de un contador o un input. - Autoguardado: Crea un
textarea
que guarde su contenido enlocalStorage
automáticamente 2 segundos después de que el usuario deje de escribir.
Top comments (0)