---
title: "Event listener or interval added in an effect without cleanup (lint/listener-without-cleanup)"
description: "lint/listener-without-cleanup: Listeners/intervals added in `useEffect` without removal stack up on every re-run and remount: duplicated handlers, leaked…"
canonical: https://void-design.vercel.app/rules/lint/listener-without-cleanup
lastModified: 2026-09-16
---

# Event listener or interval added in an effect without cleanup

`lint/listener-without-cleanup` · severity **warn** · category Lint · detected by `void lint` and `void audit`

## 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

```tsx
useEffect(() => {
  const ac = new AbortController()
  window.addEventListener('resize', onResize, { signal: ac.signal })
  return () => ac.abort()
}, [])
```

## References

- https://react.dev/reference/react/useEffect#connecting-to-an-external-system

## 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 body — [lint/timer-in-render](https://void-design.vercel.app/rules/lint/timer-in-render)
- `motionvalue-subscribe-in-render` MotionValue `.on("change")` subscription in a component body — [lint/motionvalue-subscribe-in-render](https://void-design.vercel.app/rules/lint/motionvalue-subscribe-in-render)
- `webgl-in-map` WebGL canvas rendered inside `.map()` — [lint/webgl-in-map](https://void-design.vercel.app/rules/lint/webgl-in-map)
- `raf-without-cancel` `requestAnimationFrame` loop in an effect without `cancelAnimationFrame` — [lint/raf-without-cancel](https://void-design.vercel.app/rules/lint/raf-without-cancel)
- `scroll-listener-nonpassive` `wheel` / `touchstart` / `touchmove` listener without `{ passive: true }` — [lint/scroll-listener-nonpassive](https://void-design.vercel.app/rules/lint/scroll-listener-nonpassive)
- `unload-listener` `unload` event listener — [lint/unload-listener](https://void-design.vercel.app/rules/lint/unload-listener)
- `useeffect-fetch` Page fetches its data in `useEffect` — [lint/useeffect-fetch](https://void-design.vercel.app/rules/lint/useeffect-fetch)
- `div-button` Clickable `<div>`/`<span>` without button semantics — [lint/div-button](https://void-design.vercel.app/rules/lint/div-button)

Detected by `void lint` and `void audit`. Explain it in a terminal: `void rules lint/listener-without-cleanup`
