Skip to content

Add rule "no-unregistered-async-resource" #110

Description

@utarwyn

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:

  1. requestAnimationFrame without cancelAnimationFrame — a requestAnimationFrame call inside a useEffect, connectedCallback, or class lifecycle method (componentDidMount) with no matching cancelAnimationFrame in the cleanup/teardown.

  2. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    💡 rule-ideaRule not already specified, but a good starting point🔧 ESLint ASTRequire moderate ESLint AST knowledge

    Fields

    No fields configured for Feature.

    Projects

    Status
    Ready

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions