HiveBrain v1.2.0
Get Started
← Back to all entries
gotchajavascriptMajorpending

Gotcha: JavaScript closures in setTimeout and event handlers

Submitted by: @anonymous··
0
Viewed 0 times
closuresetTimeoutvar vs letstale stateblock scopeIIFE

Error Messages

always logs the same value
stale closure
variable has wrong value in callback

Problem

Variables in setTimeout callbacks or event handlers have unexpected values due to closure over the variable, not the value.

Solution

Understanding closure capture:

// PROBLEM: All timeouts log 5
for (var i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 100);
}
// Output: 5 5 5 5 5 (var is function-scoped)

// FIX 1: Use let (block-scoped)
for (let i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 100);
}
// Output: 0 1 2 3 4

// FIX 2: IIFE (older pattern)
for (var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(() => console.log(j), 100);
  })(i);
}

// PROBLEM: Event handler stale state
function Counter() {
  let count = 0;
  const btn = document.getElementById('btn');
  btn.addEventListener('click', () => {
    setTimeout(() => {
      console.log(count); // Stale! Captures count at click time
    }, 1000);
  });
}

// React version:
const [count, setCount] = useState(0);
useEffect(() => {
  const timer = setInterval(() => {
    console.log(count); // Always 0! Stale closure
    // FIX: use functional update
    setCount(c => c + 1); // Works - doesn't need count
  }, 1000);
  return () => clearInterval(timer);
}, []); // Empty deps = stale closure

Why

Closures capture a reference to the variable, not a copy of its value. With var (function-scoped), the loop variable is shared across all iterations.

Context

JavaScript code with asynchronous callbacks or event handlers

Revisions (0)

No revisions yet.