easyReact Native#2

useEffect cleanup

Prompt

Write a component that starts an interval when mounted and increments a counter every second. Clean up the interval when the component unmounts.

Solution

const [count, setCount] = useState(0)
useEffect(() => {
  const id = setInterval(() => setCount(c => c + 1), 1000)
  return () => clearInterval(id)
}, [])
return <Text style={{ fontSize: 24 }}>{count}s</Text>
Mentor's take

Effects exist to synchronize a component with something outside React — here, a timer. The contract is symmetric: whatever the effect sets up, the returned cleanup must tear down. Without clearInterval, the interval outlives the component, keeps the JS thread ticking every second, and calls setState on an unmounted component — a memory leak plus wasted battery on a mobile device where the JS thread is a shared, contended resource.

Two details carry the interview. First, the empty dependency array means "run on mount, clean up on unmount" — the interval is created exactly once. Second, that only works because the tick uses the functional update c => c + 1. If you wrote setCount(count + 1), the closure would capture count = 0 forever and the timer would stick at 1 — the classic stale-closure interval bug. The functional form removes count from the effect's dependencies entirely.

Red flag: "fixing" the stale closure by adding count to the deps array. That tears down and recreates the interval every second — it works by accident and churns timers. The senior fix is the functional updater, which keeps the effect mount-only.

Say it: "Every subscription an effect opens, its cleanup closes — and I use functional updates so the interval never depends on state, keeping the effect mount-only."