no-leaked-event-listener
Full Name in eslint-plugin-react-web-api
react-web-api/no-leaked-event-listener
Full Name in @eslint-react/eslint-plugin
@eslint-react/web-api/no-leaked-event-listener
Features
🔍
Presets
web-api
recommended
recommended-typescript
recommended-type-checked
What it does
Enforces that every addEventListener
in a component or custom Hook has a corresponding removeEventListener
.
Adding an event listener without removing it can lead to memory leaks and unexpected behavior. This is because the event listener will continue to exist even after the component or hook is unmounted.
Examples
Failing
import React, { Component } from "react";
class MyComponent extends Component {
componentDidMount() {
document.addEventListener("click", this.handleClick);
// ^^^^^^^^^^^^^^^^
// - A 'addEventListener' in 'componentDidMount' should have a corresponding 'removeEventListener' in 'componentWillUnmount' method.
}
handleClick() {
console.log("clicked");
}
render() {
return null;
}
}
import React, { useEffect } from "react";
function MyComponent() {
useEffect(() => {
const handleClick = () => {
console.log("clicked");
};
document.addEventListener("click", handleClick);
// ^^^^^^^^^^^
// - A 'addEventListener' in 'useEffect' should have a corresponding 'removeEventListener' in its cleanup function.
}, []);
return null;
}
import React, { useEffect } from "react";
function MyComponent() {
useEffect(() => {
document.addEventListener("click", () => console.log("clicked"));
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// - A 'addEventListener' should not have an inline listener function.
}, []);
return null;
}
import React, { useEffect } from "react";
function MyComponent() {
useEffect(() => {
const handleClick = () => {
console.log("clicked");
};
document.addEventListener("click", handleClick, { capture: true });
// ^^^^^^^^^^^
// - A 'addEventListener' in 'useEffect' should have a corresponding 'removeEventListener' in its cleanup function.
return () => {
document.removeEventListener("click", handleClick, { capture: false });
};
}, []);
return null;
}
function useCustomHook() {
useEffect(() => {
const handleClick = () => {
console.log("clicked");
};
document.addEventListener("click", handleClick);
// ^^^^^^^^^^^^
// - A 'addEventListener' in 'useEffect' should have a corresponding 'removeEventListener' in its cleanup function.
}, []);
}
Passing
import React, { Component } from "react";
class MyComponent extends Component {
componentDidMount() {
document.addEventListener("click", this.handleClick);
}
componentWillUnmount() {
document.removeEventListener("click", this.handleClick);
}
handleClick() {
console.log("clicked");
}
render() {
return null;
}
}
import React, { useEffect } from "react";
function MyComponent() {
useEffect(() => {
const handleClick = () => {
console.log("clicked");
};
document.addEventListener("click", handleClick);
return () => {
document.removeEventListener("click", handleClick);
};
}, []);
return null;
}
import React, { useEffect } from "react";
function MyComponent() {
useEffect(() => {
const handleClick = () => {
console.log("clicked");
};
document.addEventListener("click", handleClick, { capture: true });
return () => {
document.removeEventListener("click", handleClick, { capture: true });
};
}, []);
return null;
}
import { useEffect } from "react";
function MyComponent() {
useEffect(() => {
const events = ["mousemove", "mousedown", "keydown", "scroll", "touchstart"];
const handleActivity = () => {};
events.forEach((event) => {
window.addEventListener(event, handleActivity);
});
return () => {
events.forEach((event) => {
window.removeEventListener(event, handleActivity);
});
};
}, []);
return null;
}
import { useEffect } from "react";
function MyComponent() {
useEffect(() => {
const events = [
["mousemove", () => {}],
["mousedown", () => {}],
];
for (const [event, handler] of events) {
window.addEventListener(event, handler);
}
return () => {
for (const [event, handler] of events) {
window.removeEventListener(event, handler);
}
};
}, []);
return null;
}
function useCustomHook() {
useEffect(() => {
const handleClick = () => {
console.log("clicked");
};
document.addEventListener("click", handleClick);
return () => {
document.removeEventListener("click", handleClick);
};
}, []);
}