Hook useEffect: Efectos secundarios en React
El hook useEffect es uno de los hooks más importantes de React. Te permite ejecutar efectos secundarios en tus componentes, es decir, operaciones que afectan algo fuera del componente como llamadas a APIs, manipulación del DOM, suscripciones a eventos, etc.
En esta clase aprenderás cómo funciona useEffect, cuándo se ejecuta, cómo controlar su ejecución con dependencias y cómo evitar errores comunes como los bucles infinitos.
¿Qué son los efectos secundarios?
Un efecto secundario es cualquier operación que afecta algo fuera del scope del componente:
- 🌐 Llamadas a APIs externas
- 📝 Modificar el DOM directamente (document.title, localStorage, etc.)
- 🔔 Suscribirse a eventos del navegador
- ⏱️ Configurar timers o intervals
- 📡 Conectarse a WebSockets
En React, los componentes deben ser funciones puras en su render, pero necesitamos una forma de ejecutar estas operaciones. Para eso existe useEffect.
Sintaxis básica de useEffect
import { useEffect } from 'react'
function MyComponent() {
useEffect(() => {
// Código del efecto
console.log('El efecto se ejecutó')
})
return <div>Mi componente</div>
}
¿Cuándo se ejecuta useEffect?
Sin segundo parámetro: En cada render
Si no pasas un segundo parámetro, el efecto se ejecuta en cada render del componente:
function App() {
const [count, setCount] = useState(0)
useEffect(() => {
console.log('El componente se renderizó')
})
return (
<div>
<p>Contador: {count}</p>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
</div>
)
}
Cada vez que haces clic en el botón:
1. El estado count cambia
2. El componente se re-renderiza
3. useEffect se ejecuta
4. Se imprime "El componente se renderizó"
⚠️ Cuidado: Esto puede causar problemas de rendimiento si el efecto es costoso.
El array de dependencias
El segundo parámetro de useEffect es un array de dependencias que controla cuándo se ejecuta el efecto:
useEffect(() => {
// Código del efecto
}, [dependencia1, dependencia2])
Array vacío: Solo en el primer render
Si pasas un array vacío [], el efecto se ejecuta solo una vez cuando el componente se monta:
function App() {
const [count, setCount] = useState(0)
useEffect(() => {
console.log('Solo se ejecuta al montar el componente')
}, [])
return (
<div>
<p>Contador: {count}</p>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
</div>
)
}
Ahora, aunque hagas clic 100 veces en el botón, el efecto solo se ejecuta una vez al inicio.
Con dependencias: Solo cuando cambian
Si pasas dependencias específicas, el efecto se ejecuta:
- En el primer render
- Cada vez que alguna dependencia cambia
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('Miguel')
useEffect(() => {
console.log('El contador cambió:', count)
}, [count])
return (
<div>
<p>Contador: {count}</p>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
<input value={name} onChange={(e) => setName(e.target.value)} />
</div>
)
}
En este ejemplo:
- ✅ El efecto se ejecuta cuando
countcambia - ❌ El efecto NO se ejecuta cuando
namecambia - ✅ El efecto se ejecuta en el primer render
Múltiples dependencias
Puedes tener múltiples dependencias. El efecto se ejecutará si cualquiera de ellas cambia:
function JobListings() {
const [currentPage, setCurrentPage] = useState(1)
const [textToFilter, setTextToFilter] = useState('')
useEffect(() => {
console.log('Página o filtro cambiaron')
console.log('Página:', currentPage)
console.log('Filtro:', textToFilter)
}, [currentPage, textToFilter])
return (
<div>
<input
value={textToFilter}
onChange={(e) => setTextToFilter(e.target.value)}
placeholder="Filtrar trabajos..."
/>
<button onClick={() => setCurrentPage(currentPage + 1)}>Siguiente página</button>
</div>
)
}
El efecto se ejecuta cuando:
- ✅ Cambias el texto del filtro
- ✅ Cambias de página
- ✅ El componente se monta por primera vez
Evitar bucles infinitos ⚠️
Uno de los errores más comunes con useEffect es crear bucles infinitos. Esto ocurre cuando el efecto modifica una de sus propias dependencias:
❌ Mal: Bucle infinito
function App() {
const [count, setCount] = useState(0)
useEffect(() => {
// 🚨 ¡BUCLE INFINITO!
setCount(count + 1)
}, [count])
return <div>Contador: {count}</div>
}
¿Qué pasa?
1. El componente se monta → useEffect se ejecuta
2. setCount(count + 1) → count cambia
3. count cambió y es una dependencia → useEffect se ejecuta
4. setCount(count + 1) → count cambia
5. count cambió y es una dependencia → useEffect se ejecuta
6. ... infinito
✅ Solución 1: Eliminar la dependencia
Si no necesitas que el efecto se ejecute cuando cambia count, elimínalo de las dependencias:
useEffect(() => {
// Solo se ejecuta una vez
setCount(1)
}, [])
✅ Solución 2: Usar la función callback de setState
Si necesitas el valor anterior, usa la forma funcional de setState:
useEffect(() => {
// Correcto: no depende de count
setCount((prevCount) => prevCount + 1)
}, [])
✅ Solución 3: No modifiques las dependencias
Si count es una dependencia, no lo modifiques dentro del efecto:
useEffect(() => {
// Solo leer count, no modificarlo
console.log('El contador es:', count)
document.title = `Contador: ${count}`
}, [count])
Ejemplo práctico: Actualizar el título del documento
Vamos a crear un efecto que cambia el título de la pestaña del navegador según los filtros y la página actual:
function App() {
const [currentPage, setCurrentPage] = useState(1)
const [textToFilter, setTextToFilter] = useState('')
const [jobsData, setJobsData] = useState([])
// Filtrar trabajos
const jobsFiltered =
textToFilter === ''
? jobsData
: jobsData.filter((job) => job.title.toLowerCase().includes(textToFilter.toLowerCase()))
// Efecto para actualizar el título
useEffect(() => {
const jobCount = jobsFiltered.length
if (textToFilter === '') {
document.title = `DevJobs - Página ${currentPage}`
} else {
document.title = `${jobCount} trabajos de "${textToFilter}" - Página ${currentPage}`
}
}, [jobsFiltered.length, currentPage, textToFilter])
return (
<div>
<input
value={textToFilter}
onChange={(e) => setTextToFilter(e.target.value)}
placeholder="Filtrar trabajos..."
/>
<div>
{jobsFiltered.map((job) => (
<div key={job.id}>{job.title}</div>
))}
</div>
<button onClick={() => setCurrentPage(currentPage - 1)} disabled={currentPage === 1}>
Anterior
</button>
<span>Página {currentPage}</span>
<button onClick={() => setCurrentPage(currentPage + 1)}>Siguiente</button>
</div>
)
}
Ahora, cuando:
- 🔍 Escribes en el filtro → el título se actualiza con el número de resultados
- 📄 Cambias de página → el título muestra la página actual
- 🧹 Borras el filtro → el título vuelve a mostrar solo la página
Reglas importantes de useEffect
1. Siempre se ejecuta al menos una vez
El efecto siempre se ejecuta en el primer render, sin importar las dependencias:
useEffect(() => {
console.log('Se ejecuta en el primer render')
}, [count])
Aunque count no haya cambiado, el efecto se ejecuta al montar el componente.
2. Las dependencias deben incluir todo lo que uses
Si usas variables, props o estado dentro del efecto, debes incluirlos en las dependencias:
// ❌ Mal: falta textToFilter en las dependencias
useEffect(() => {
console.log('Filtro:', textToFilter)
}, [])
// ✅ Bien: incluimos todas las variables que usamos
useEffect(() => {
console.log('Filtro:', textToFilter)
}, [textToFilter])
3. No uses objetos o arrays como dependencias directamente
Los objetos y arrays se crean de nuevo en cada render, lo que puede causar que el efecto se ejecute más de lo esperado:
const filters = { technology: 'react', location: 'remote' }
// ❌ Mal: filters es un objeto nuevo en cada render
useEffect(() => {
console.log('Filtros:', filters)
}, [filters])
Solución: Usa las propiedades individuales como dependencias:
// ✅ Bien: usamos las propiedades
useEffect(() => {
console.log('Filtros:', filters.technology, filters.location)
}, [filters.technology, filters.location])
4. Evita modificar las dependencias dentro del efecto
Como vimos antes, esto puede causar bucles infinitos:
// ❌ Mal: modifica su propia dependencia
useEffect(() => {
setCount(count + 1)
}, [count])
// ✅ Bien: no modifica sus dependencias
useEffect(() => {
console.log('Count:', count)
}, [count])
Resumen de la matriz de dependencias
| Dependencias | ¿Cuándo se ejecuta el efecto? |
|---|---|
| Sin segundo parámetro | En cada render |
[] (array vacío) | Solo una vez (al montar) |
[dep1] | Al montar + cada vez que dep1 cambie |
[dep1, dep2] | Al montar + cada vez que dep1 o dep2 cambien |
Conclusión
El hook useEffect es esencial para ejecutar efectos secundarios en React:
- 🎯 Controla cuándo se ejecuta con el array de dependencias
- 🔄 Evita bucles infinitos no modificando las dependencias dentro del efecto
- 🧹 Haz cleanup cuando sea necesario
- 📦 Domina la matriz de dependencias para evitar bugs
En las próximas clases veremos hooks más avanzados y aprenderemos a crear nuestros propios custom hooks para reutilizar lógica de efectos.
💡 Recuerda: El efecto siempre se ejecuta al menos una vez (en el primer render), y después solo según las dependencias que le indiques.