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:
useEffectve 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
countcambia, 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
-
useEffectve el mundo como era cuando nació. -
Las dependencias permiten “renacer” el efecto cuando cambian variables importantes.
-
useRefpermite leer valores actuales sin recrear efectos. -
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


