Why it matters
Listeners/intervals added in useEffect without removal stack up on every re-run and remount: duplicated handlers, leaked components and degraded INP over a session.
How to fix it
Return a cleanup that calls removeEventListener/clearInterval, or pass { signal } from an AbortController and abort it.
Example
useEffect(() => {
const ac = new AbortController()
window.addEventListener('resize', onResize, { signal: ac.signal })
return () => ac.abort()
}, [])References
More lint rules
void lint reports 67 rules in this category. Static source checks with no browser: Tailwind v4 silent failures, Next.js 16 API traps, React render-body bugs, accessibility markup, SEO files and motion hygiene.
timer-in-render`setTimeout` / `setInterval` called in a component bodymotionvalue-subscribe-in-renderMotionValue `.on("change")` subscription in a component bodywebgl-in-mapWebGL canvas rendered inside `.map()`raf-without-cancel`requestAnimationFrame` loop in an effect without `cancelAnimationFrame`scroll-listener-nonpassive`wheel` / `touchstart` / `touchmove` listener without `{ passive: true }`unload-listener`unload` event listeneruseeffect-fetchPage fetches its data in `useEffect`div-buttonClickable `<div>`/`<span>` without button semantics