Solution you'd like
Add a new ESLint rule no-unregistered-async-resource that detects asynchronous resources registered in a React component lifecycle that are never properly cleaned up. This covers two closely related patterns:
-
requestAnimationFrame without cancelAnimationFrame — a requestAnimationFrame call inside a useEffect, connectedCallback, or class lifecycle method (componentDidMount) with no matching cancelAnimationFrame in the cleanup/teardown.
-
addEventListener without removeEventListener — an addEventListener call inside the same lifecycle contexts with no matching removeEventListener in the cleanup return or componentWillUnmount.
Examples of code to flag:
// ❌ Animation loop never cancelled on unmount
useEffect(() => {
const loop = () => { draw(); requestAnimationFrame(loop); };
requestAnimationFrame(loop);
}, []);
// ❌ Listener never removed on unmount
useEffect(() => {
window.addEventListener('resize', handleResize);
}, []);
Expected patterns:
// ✅
useEffect(() => {
let id;
const loop = () => { draw(); id = requestAnimationFrame(loop); };
id = requestAnimationFrame(loop);
return () => cancelAnimationFrame(id);
}, []);
// ✅
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
Is your feature request related to a problem?
Forgetting to cancel a requestAnimationFrame loop or to remove an event listener when a component is destroyed causes resource leaks: the animation loop keeps running and event handlers keep firing long after the component is gone, wasting CPU and memory.
Alternatives you've considered
These two patterns were initially considered as two separate rules, but they share the same root cause (an async resource registered without a paired teardown in a lifecycle) and the same detection strategy, making a single combined rule more maintainable.
Additional Information
Detection scope: useEffect callbacks (React), connectedCallback / disconnectedCallback pairs (Web Components), and componentDidMount / componentWillUnmount pairs (React class components).
Solution you'd like
Add a new ESLint rule
no-unregistered-async-resourcethat detects asynchronous resources registered in a React component lifecycle that are never properly cleaned up. This covers two closely related patterns:requestAnimationFramewithoutcancelAnimationFrame— arequestAnimationFramecall inside auseEffect,connectedCallback, or class lifecycle method (componentDidMount) with no matchingcancelAnimationFramein the cleanup/teardown.addEventListenerwithoutremoveEventListener— anaddEventListenercall inside the same lifecycle contexts with no matchingremoveEventListenerin the cleanup return orcomponentWillUnmount.Examples of code to flag:
Expected patterns:
Is your feature request related to a problem?
Forgetting to cancel a
requestAnimationFrameloop or to remove an event listener when a component is destroyed causes resource leaks: the animation loop keeps running and event handlers keep firing long after the component is gone, wasting CPU and memory.Alternatives you've considered
These two patterns were initially considered as two separate rules, but they share the same root cause (an async resource registered without a paired teardown in a lifecycle) and the same detection strategy, making a single combined rule more maintainable.
Additional Information
Detection scope:
useEffectcallbacks (React),connectedCallback/disconnectedCallbackpairs (Web Components), andcomponentDidMount/componentWillUnmountpairs (React class components).