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

  1. Store the listener once and pass the same reference to add and remove: `const onClick = () => save(); el.addEventListener('click', onClick); el.removeEventListener('click', onClick);`
  2. For methods needing `this`, bind once (constructor or class field: `this.onClick = this.onClick.bind(this)`) and use `this.onClick` for both calls
  3. 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

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


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/bedb6cf1a539e00f. Report an issue: GitHub.