Introducción

En esta publicación vamos a explicar un problema muy común en React: los stale closures dentro de useEffect.
Aprenderás a:

  • Identificar cuando tu efecto está viendo datos antiguos

  • Corregirlo usando dependencias en el array

  • Usar useRef para mantener valores actualizados sin recrear el efecto

Al final, tendrás tres ejemplos prácticos que puedes copiar y probar.

Qué es un stale closure

Un stale closure ocurre cuando:

  • Un efecto (useEffect) captura un valor del render inicial

  • Ese valor cambia después, pero el efecto sigue usando el valor antiguo


/*
Render → useEffect se crea → closure captura count = 0

Usuario incrementa count → componente re-renderiza

useEffect sigue viendo count = 0
*/

💡 En palabras simples:

useEffect ve el mundo como era cuando nació.

Ejemplo 1: Bug clásico (sin dependencias)


import { useState, useEffect, useRef } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);
  countRef.current = count;

  useEffect(() => {
    const id = setInterval(() => {
      console.log(countRef.current);
    }, 1000);

    return () => clearInterval(id);
  }, []);

  return (
    

Count: {count}

); }

Qué pasa:

  • El contador en pantalla sube

  • La consola siempre imprime 0

  • ESLint avisa: «React Hook useEffect has a missing dependency: ‘count'»

Ejemplo 2: Solución usando dependencias


useEffect(() => {
const id = setInterval(() => {
console.log('Valor actualizado con dependencias:', count);
}, 1000);

return () => clearInterval(id);
}, [count]); // ✅ ahora count está en dependencias

Qué pasa:

  • Cada vez que count cambia, el efecto se limpia y se crea uno nuevo

  • La consola imprime siempre el valor correcto

  • ⚠️ Cuidado: si el efecto es pesado, recrearlo cada vez puede ser costoso

Ejemplo 3: Solución avanzada usando useRef


'use client';
import { useState, useEffect, useRef } from 'react';

export default function StaleClosureRef() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);

  countRef.current = count; // update the ref on each render

  useEffect(() => {
    const id = setInterval(() => {
      console.log('Value updated with ref:', countRef.current);
    }, 1000);

    return () => clearInterval(id);
  }, []); // ✅ effect created only once

  return (
    

Count: {count}

); }

Qué pasa:

  • El efecto se ejecuta una sola vez

  • La consola imprime siempre el valor actualizado

  • Más eficiente que recrear el efecto en cada cambio

Conclusión y regla de oro

  1. useEffect ve el mundo como era cuando nació.

  2. Las dependencias permiten “renacer” el efecto cuando cambian variables importantes.

  3. useRef permite leer valores actuales sin recrear efectos.

  4. Siempre limpia recursos como intervalos, listeners o fetches pendientes.

Frase clave:
“Dependencias → renacer del efecto. useRef → leer el presente sin renacer.”

Bonus para producción

  • Para intervalos, animaciones, polling y WebSockets, useRef es muy recomendable

  • Para fetches dependientes de filtros o props, usa dependencias correctas

  • Nunca ignores los warnings de ESLint sin entender la razón