Efectos secundarios y solicitudes de datos — useEffect
En este capítulo aprenderás el Hook de efectos secundarios más importante de React —useEffect—, y cargarás datos de blog desde un archivo JSON con fetch en el momento adecuado.
¿Qué es un efecto secundario?
En React,un efecto secundario (Side Effect)se refiere a aquellas operaciones que no están relacionadas con el renderizado:
- Solicitar datos al servidor
- Operar con localStorage
- Configurar un temporizador setInterval
- Modificar el DOM manualmente
- Suscribirse a eventos externos
Los efectos secundarios no pueden ejecutarse directamente en el cuerpo de la función del componente (haría que se ejecutaran en cada renderizado), deben colocarse enuseEffect.
Uso básico de useEffect
Ejemplo
function HomePage() {
const [message, setMessage] = useState('Cargando...')
// useEffect(fn, deps): ejecuta el efecto secundario después de que el componente se renderice
useEffect(() => {
console.log('El componente se ha montado en el DOM, ya se pueden ejecutar efectos secundarios')
setMessage('¡Carga de datos completada!')
}, []) // Array de dependencias vacío = se ejecuta solo una vez después del primer renderizado
return <p>{message}</p>
}
Tres formas de escribir el array de dependencias
| Forma | Momento de ejecución | Caso de uso |
|---|---|---|
useEffect(fn) | Se ejecuta después de cada renderizado | Se usa muy raramente, puede causar bucles infinitos |
useEffect(fn, []) | Se ejecuta solo una vez después del primer renderizado | Carga de datos, inicialización de librerías de terceros |
useEffect(fn, [dep]) | Se ejecuta después del primer renderizado + cuando dep cambia | Escuchar cambios de datos específicos y ejecutar operaciones de respuesta |
Diferencia entre omitir el array de dependencias vs array vacío:
useEffect(fn)Se ejecuta después de cada renderizado;useEffect(fn, [])Se ejecuta solo una vez después del primer renderizado. El primero puede causar fácilmente bucles infinitos (setState en effect → dispara renderizado → ejecuta effect nuevamente → ...), casi nunca se usa.
Cargar datos JSON con fetch
Al igual que en Vue3, colocamos los datos de los artículos enpublic/posts.jsonel archivo.
Primer paso: crear el archivo de datos
Ejemplo
[
{
"id": 1,
"title": "Guía completa de introducción a React",
"summary": "Aprende React Hooks desde cero, cubriendo conceptos clave como useState, useEffect.",
"content": "<h2>¿Por qué aprender React?</h2><p>React es uno de los frameworks frontend más populares actualmente...</p>",
"category": "React",
"date": "2024-05-10"
},
{
"id": 2,
"title": "Programación asíncrona en JavaScript en detalle",
"summary": "Entiende de una vez Promise, async/await, el bucle de eventos y la cola de microtareas.",
"content": "<h2>¿Qué es la asincronía?</h2><p>JS es de un solo hilo...</p>",
"category": "JavaScript",
"date": "2024-05-08"
},
{
"id": 3,
"title": "Práctica de diseño con CSS Grid",
"summary": "Implementa fácilmente diseños responsive complejos con CSS Grid.",
"content": "<h2>Introducción a Grid</h2><p>Grid es un sistema de diseño bidimensional...</p>",
"category": "CSS",
"date": "2024-05-05"
}
]
Segundo paso: cargar datos en useEffect
Ejemplo
import { useState, useEffect, useMemo } from 'react'
import BlogCard from '../components/BlogCard'
import CategoryFilter from '../components/CategoryFilter'
function HomePage() {
const [articles, setArticles] = useState([]) // Datos de los artículos
const [isLoading, setIsLoading] = useState(true) // Estado de carga
const [error, setError] = useState(null) // Mensaje de error
const [activeCategory, setActiveCategory] = useState('Todos')
// useEffect carga datos: array de dependencias vacío = se ejecuta solo una vez después del primer renderizado
useEffect(() => {
let cancelled = false // Marcador para evitar setState después de que el componente se desmonte
async function fetchPosts() {
setIsLoading(true)
setError(null)
try {
const res = await fetch('/posts.json')
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const data = await res.json()
if (!cancelled) {
setArticles(data)
}
} catch (err) {
if (!cancelled) {
setError(err.message)
}
} finally {
if (!cancelled) {
setIsLoading(false)
}
}
}
fetchPosts()
// Función de limpieza: establece cancelled en true cuando el componente se desmonta
return () => { cancelled = true }
}, [])
const categories = useMemo(() => {
return ['Todos', ...new Set(articles.map(a => a.category))]
}, [articles])
const filteredArticles = useMemo(() => {
if (activeCategory === 'Todos') return articles
return articles.filter(a => a.category === activeCategory)
}, [articles, activeCategory])
return (
<div>
<h2 className="section-title">Últimos artículos</h2>
{/* Cargando */}
{isLoading && <p className="status-msg">Cargando, por favor espere...</p>}
{/* Error al cargar */}
{error && (
<div className="status-msg error">
<p>Error al cargar:{error}</p>
<button onClick={() => window.location.reload()}>Reintentar</button>
</div>
)}
{/* Carga de datos completada */}
{!isLoading && !error && (
<>
<CategoryFilter
categories={categories}
activeCategory={activeCategory}
onCategoryChange={setActiveCategory}
/>
<p className="result-info">común{filteredArticles.length}artículo</p>
{filteredArticles.length === 0 ? (
<p className="empty-tip">No hay artículos en esta categoría</p>
) : (
<div className="article-grid">
{filteredArticles.map(article => (
<BlogCard key={article.id} {...article} />
))}
</div>
)}
</>
)}
</div>
)
}
export default HomePage
cancelledEl marcador es un patrón común en React. Cuando fetch aún está en curso, si el usuario abandona la página (el componente se desmonta), el setState posterior generará una advertencia de error. Establecer en la función cleanupcancelled = truepermite omitir setState después del desmontaje.
Función de limpieza (Cleanup)
La función de retorno de useEffect puede devolver una función, llamadafunción de limpieza。
Se ejecuta en dos momentos:Cuando el componente se desmontayAntes de la próxima ejecución del effect。
Ejemplo
// Efecto secundario: suscripción a eventos
const handleScroll = () => console.log('Se ha desplazado')
window.addEventListener('scroll', handleScroll)
// Devuelve la función de limpieza
return () => {
// Elimina automáticamente el listener de eventos al desmontar el componente, evitando fugas de memoria
window.removeEventListener('scroll', handleScroll)
}
}, [])
useEffect(() => {
// Efecto secundario: configurar un temporizador
const timer = setInterval(() => {
console.log('Se ejecuta cada segundo')
}, 1000)
return () => clearInterval(timer) // Limpiar el temporizador
}, [])
| Efecto secundario | ¿Requiere limpieza? | Método de limpieza |
|---|---|---|
| Solicitud fetch | Sí | Usar el marcador cancelled o AbortController |
| Escucha de eventos | Sí | removeEventListener |
| Temporizador | Sí | clearInterval / clearTimeout |
| Restaurar después de modificar el DOM | Sí | Restaurar el estado original en cleanup |
| Escritura en localStorage | no | No requiere limpieza |
Cuatro estados para manejar solicitudes asíncronas
Cualquier solicitud de datos debería cubrir los siguientes cuatro estados de UI:
| Estado | Condición | UI |
|---|---|---|
| Cargando | isLoading === true | Animación de loading, pantalla esqueleto |
| Éxito | Los datos han sido devueltos | Renderizar el contenido normalmente |
| Error | error !== null | Mensaje de error + botón de reintentar |
| Datos vacíos | Los datos son un array vacío | Mensaje de estado vacío |
Comparación con el ciclo de vida de Vue3
| Vue3 | React | Descripción |
|---|---|---|
onMounted | useEffect(fn, []) | Se ejecuta después del primer renderizado |
watch(keyword, fn) | useEffect(fn, [keyword]) | Responde a cambios de datos específicos |
onBeforeUnmount | useEffect 的 cleanup | Limpieza antes de que el componente se destruya |
| No tiene correspondencia directa | useEffect(() => { fn; return cleanup }, [dep]) | Cuando dep cambia, primero cleanup y luego fn |
Resumen del capítulo
En este capítulo has dominado el uso principal de useEffect: las tres formas de escribir el array de dependencias, la carga asíncrona de datos con fetch, el manejo de los cuatro estados de UI y el papel de la función de limpieza cleanup.
Ahora los datos del blog se cargan dinámicamente desde un JSON externo, ya no están codificados en JS.
Otras extensiones