Saltar al contenido
schedule 12 min React

Efectos con useEffect

Con useState ya puedes hacer que tus componentes tengan memoria. Pero hay cosas que un componente necesita hacer que no son simplemente "mostrar datos": llamar a una API, cambiar el título de la página, configurar un temporizador, escuchar un evento del navegador. Estas operaciones se llaman efectos secundarios (side effects), y para ellas existe useEffect.

Piénsalo así: el componente tiene un trabajo principal (renderizar JSX) y trabajos secundarios (todo lo demás). useEffect es donde pones esos trabajos secundarios.

Sintaxis básica

import { useEffect } from 'react'

useEffect(() => {
    // Este código se ejecuta después de que React
    // renderiza el componente en pantalla
    console.log('El componente se ha renderizado')
})

useEffect recibe una función que se ejecuta después de cada renderizado del componente. Pero normalmente no quieres que se ejecute siempre — quieres controlarlo. Para eso está el segundo argumento: el array de dependencias.

El array de dependencias

El segundo argumento de useEffect controla cuándo se ejecuta el efecto:

// 1. Sin array: se ejecuta después de CADA renderizado
useEffect(() => {
    console.log('Cada renderizado')
})

// 2. Array vacío: se ejecuta solo UNA vez (al montarse)
useEffect(() => {
    console.log('Solo al montar el componente')
}, [])

// 3. Con dependencias: se ejecuta al montar Y cuando cambian las dependencias
useEffect(() => {
    console.log('El nombre ha cambiado:', nombre)
}, [nombre])

El array vacío [] es el más común cuando quieres hacer algo solo una vez — como llamar a una API cuando el componente aparece en pantalla.

Ejemplo: cambiar el título de la página

src/components/Contador.jsx
import { useState, useEffect } from 'react'

function Contador() {
    const [cuenta, setCuenta] = useState(0)

    // Cada vez que cambia "cuenta", actualiza el título de la pestaña
    useEffect(() => {
        document.title = `Café Estelar (${cuenta} pedidos)`
    }, [cuenta])

    return (
        <div>
            <p>Pedidos: {cuenta}</p>
            <button onClick={() => setCuenta(prev => prev + 1)}>
                Nuevo pedido
            </button>
        </div>
    )
}

export default Contador

Cada vez que cuenta cambia, React vuelve a ejecutar el efecto y el título de la pestaña del navegador se actualiza. Fíjate en que [cuenta] le dice a React: "solo vuelve a ejecutar este efecto cuando cuenta cambie".

Ejemplo: ejecutar código al montar

Un patrón muy habitual es cargar datos cuando el componente aparece por primera vez:

src/components/MensajeBienvenida.jsx
import { useState, useEffect } from 'react'

function MensajeBienvenida() {
    const [hora, setHora] = useState('')

    useEffect(() => {
        // Se ejecuta solo una vez, al montar el componente
        const horaActual = new Date().getHours()

        if (horaActual < 12) {
            setHora('Buenos días')
        } else if (horaActual < 20) {
            setHora('Buenas tardes')
        } else {
            setHora('Buenas noches')
        }
    }, []) // ← Array vacío = solo al montar

    return <h2>{hora}, bienvenido a Café Estelar</h2>
}

export default MensajeBienvenida

Cleanup: limpiar al desmontar

Algunos efectos necesitan "limpiarse" cuando el componente se desmonta o antes de ejecutarse de nuevo. Por ejemplo: temporizadores, suscripciones a eventos, o conexiones WebSocket. Para esto, el efecto devuelve una función de limpieza (cleanup).

src/components/Reloj.jsx
import { useState, useEffect } from 'react'

function Reloj() {
    const [hora, setHora] = useState(new Date())

    useEffect(() => {
        // Configurar el intervalo
        const intervalo = setInterval(() => {
            setHora(new Date())
        }, 1000)

        // Función de limpieza: se ejecuta al desmontar
        return () => {
            clearInterval(intervalo)
        }
    }, []) // Solo al montar

    return (
        <p>
            Hora actual: {hora.toLocaleTimeString('es-ES')}
        </p>
    )
}

export default Reloj

Sin la función de limpieza, cada vez que el componente se desmonta y se vuelve a montar, se crearía un nuevo intervalo sin eliminar el anterior. Después de unas cuantas veces, tendrías docenas de intervalos ejecutándose simultáneamente — un memory leak.

Ejemplo: escuchar eventos del navegador

src/components/AnchoVentana.jsx
import { useState, useEffect } from 'react'

function AnchoVentana() {
    const [ancho, setAncho] = useState(window.innerWidth)

    useEffect(() => {
        function handleResize() {
            setAncho(window.innerWidth)
        }

        // Añadir listener
        window.addEventListener('resize', handleResize)

        // Cleanup: quitar listener al desmontar
        return () => {
            window.removeEventListener('resize', handleResize)
        }
    }, [])

    return <p>Ancho de la ventana: {ancho}px</p>
}

export default AnchoVentana

Efecto con dependencias que cambian

Cuando el array de dependencias tiene valores que cambian, React ejecuta la limpieza del efecto anterior antes de ejecutar el nuevo:

src/components/Buscador.jsx
import { useState, useEffect } from 'react'

function Buscador() {
    const [termino, setTermino] = useState('')
    const [resultados, setResultados] = useState([])

    useEffect(() => {
        // No buscar si el término está vacío
        if (!termino.trim()) {
            setResultados([])
            return
        }

        // Simular una búsqueda con un timeout
        const timeout = setTimeout(() => {
            console.log('Buscando:', termino)
            // Aquí irían los resultados reales de una API
            setResultados([`Resultado para "${termino}"`])
        }, 500)

        // Cleanup: cancela el timeout anterior si el usuario sigue escribiendo
        return () => clearTimeout(timeout)
    }, [termino]) // Se ejecuta cada vez que cambia "termino"

    return (
        <div>
            <input
                value={termino}
                onChange={(e) => setTermino(e.target.value)}
                placeholder="Buscar café..."
            />
            <ul>
                {resultados.map((r, i) => <li key={i}>{r}</li>)}
            </ul>
        </div>
    )
}

export default Buscador

Este patrón se llama debouncing: esperar a que el usuario deje de escribir antes de ejecutar la búsqueda. El cleanup cancela el timeout anterior cada vez que el usuario pulsa una tecla, y el timeout solo se completa cuando el usuario para de escribir durante 500ms.

Cuándo NO usar useEffect

Un error muy común en React es abusar de useEffect. No lo necesitas para:

// ❌ NO: calcular estado derivado con useEffect
const [items, setItems] = useState([...])
const [total, setTotal] = useState(0)

useEffect(() => {
    setTotal(items.reduce((sum, item) => sum + item.precio, 0))
}, [items])

// ✅ SÍ: calcúlalo directamente (estado derivado)
const [items, setItems] = useState([...])
const total = items.reduce((sum, item) => sum + item.precio, 0)


// ❌ NO: transformar datos durante el render con useEffect
useEffect(() => {
    setNombreMayusculas(nombre.toUpperCase())
}, [nombre])

// ✅ SÍ: calcúlalo directamente
const nombreMayusculas = nombre.toUpperCase()

La regla es simple: si puedes calcularlo durante el render, no uses useEffect. Reserva useEffect para operaciones que interactúan con el mundo exterior: APIs, DOM, temporizadores, localStorage, eventos del navegador.

Resumen

  • useEffect(fn, deps) ejecuta código después del renderizado, controlado por el array de dependencias.
  • Array vacío []: solo al montar. Sin array: cada render. Con valores: cuando esos valores cambian.
  • La función de cleanup (el return del efecto) se ejecuta al desmontar o antes de re-ejecutar el efecto.
  • Usa cleanup para limpiar temporizadores, listeners y suscripciones.
  • No abuses de useEffect: si puedes calcular un valor durante el render, hazlo directamente.
  • Usa useEffect para operaciones con el mundo exterior: APIs, DOM, localStorage, eventos del navegador.

En la siguiente lección aprenderás a manejar eventos y formularios en React — cómo capturar datos del usuario, validar inputs y construir formularios controlados completos.

code

Reloj y guardado automático

Intermedio schedule 20 min

Crea un componente NotasEstelar que combine varios efectos:

  • Un textarea donde el usuario escribe notas. Guarda automáticamente el contenido en localStorage con un debounce de 1 segundo (usa useEffect con cleanup de setTimeout).
  • Al montar el componente, carga las notas guardadas de localStorage (si existen).
  • Muestra un reloj en tiempo real debajo del textarea (usando setInterval con su cleanup correspondiente).
  • Cambia el título de la pestaña a "Notas Estelar — [número] caracteres" cada vez que el texto cambie.
  • Muestra un indicador "Guardando..." que aparece cuando el usuario escribe y desaparece después del guardado.
lightbulb Pistas

Necesitarás tres useEffect separados: uno para el reloj (con setInterval y cleanup), otro para el debounce de guardado (con setTimeout y cleanup), y otro para el título de la pestaña. Para el indicador "Guardando...", usa un estado booleano que se active cuando el usuario escribe y se desactive dentro del setTimeout cuando el guardado se completa.

Newsletter

Recibe nuevos cursos, actualizaciones, artículos del blog y promociones en tu correo.