oxc-project/oxc · error · OxcDiagnostic
Invalid `removeEventListener` call.
Error message
Invalid `removeEventListener` call.
What it means
Diagnostic from the oxlint rule `unicorn/no-invalid-remove-event-listener` (category: correctness). `removeEventListener(type, listener)` only removes the listener whose exact reference was passed to `addEventListener`. Passing a freshly created function — an inline arrow/function expression or the result of a `.bind()` call — creates a brand-new reference that matches nothing, so the removal is a silent no-op and the listener leaks. The rule flags exactly these shapes; a stored reference such as `handler` or `obj.method` passes.
Source
Thrown at crates/oxc_linter/src/rules/unicorn/no_invalid_remove_event_listener.rs:12
use oxc_ast::{
AstKind,
ast::{Argument, MemberExpression},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use crate::{AstNode, context::LintContext, rule::Rule};
fn no_invalid_remove_event_listener_diagnostic(call_span: Span, arg_span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Invalid `removeEventListener` call.")
.with_help("The listener argument should be a function reference.")
.with_labels([
call_span.label("`removeEventListener` called here."),
arg_span.label("Invalid argument here"),
])
}
#[derive(Debug, Default, Clone)]
pub struct NoInvalidRemoveEventListener;
declare_oxc_lint!(
/// ### What it does
///
/// It warns when you use a non-function value as the second argument of `removeEventListener`.
///
/// ### Why is this bad?
///
/// The [`removeEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener) function must be called with a reference to the same function that was passed to [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener). Calling `removeEventListener` with an inline function or the result of an inline `.bind()` call is indicative of an error, and won't actually remove the listener.View on GitHub (pinned to e1e7af627c)
Solutions
- Store the listener once and pass the same reference to add and remove: `const onClick = () => save(); el.addEventListener('click', onClick); el.removeEventListener('click', onClick);`
- For methods needing `this`, bind once (constructor or class field: `this.onClick = this.onClick.bind(this)`) and use `this.onClick` for both calls
- Prefer an AbortController: `const ac = new AbortController(); el.addEventListener('click', fn, { signal: ac.signal }); ac.abort();` removes everything
Example fix
// before
el.addEventListener('click', () => save());
el.removeEventListener('click', () => save()); // never removes anything
// after
const onClick = () => save();
el.addEventListener('click', onClick);
el.removeEventListener('click', onClick); Defensive patterns
Strategy: validation
Validate before calling
// keep add/remove symmetrical through one helper
const registered = new WeakMap();
function on(target, type, fn, options) {
target.addEventListener(type, fn, options);
const set = registered.get(target) ?? new Set();
set.add([type, fn, options]);
registered.set(target, set);
}
function offAll(target) {
for (const [type, fn, options] of registered.get(target) ?? []) {
target.removeEventListener(type, fn, options);
}
registered.delete(target);
} Prevention
- Register listeners only through named references or AbortController signals (`ac.abort()` removes them all)
- Never pass an inline function or `.bind()` result to removeEventListener — new reference, silent no-op
- Do teardown in the same effect/lifecycle scope that did setup
When it happens
Trigger: `el.removeEventListener('click', () => handle())`, `window.removeEventListener('scroll', this.onScroll.bind(this))`, `el.removeEventListener('keydown', function (e) {})` — the second argument is a function expression, arrow function, or `anything.bind(...)` call.
Common situations: Component teardown where handlers were originally added inline; class code binding methods at removal time instead of storing the bound reference at add time; cleanup paths written far from the registration code.
Related errors
- Prefer `addEventListener()` over their `on`-function counter
- The update clause in this loop moves the variable in the wro
- Unexpected assignment to `exports`.
- Unexpected assignment to 'exports'.
- Avoid using `.length` as the index in `Array#with()`.
AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20).
Data as JSON: /api/errors/bedb6cf1a539e00f.
Report an issue: GitHub.