listener-added-by-hand
This fails the run. Where it is wrong about your code, // ramonda-check-ignore <reason> on the line says so, and the reason is printed on every run.
Reported when a component adds a window or document listener by hand, where @onWindow or @onDocument would do it — or, inside if (__DEV__) where a decorator cannot be used, adds one that nothing ever removes.
@onWindow and @onDocument attach on mount and detach on unmount, and there is nothing to
remember:
@onWindow("resize") onResize() { … }
A hand-rolled listener has to be removed by hand, and one that is not outlives the component that added it: the handler keeps running, reading state nobody is showing and holding the component and everything it closed over alive. Open and close the same view ten times and there are ten of them.
In a render() it is worse than it looks — a render runs whenever the framework likes, so
the listener is registered again on every pass.
A listener the app ARMS — on a click, after a fetch — is the Listener hook. @onWindow
attaches for the owner's whole life and cannot be turned on and off; Listener can, and the
framework still removes it when the owner goes:
private escape = this.use(Listener, () => ({
on: "document",
type: "keydown",
run: this.onKey,
}));
@mounted open() { this.escape.listen(); }
close() { this.escape.stop(); }
One hook instance is one listener, and teardown removes it — there is nothing to remember and
no handler reference to keep in step. It is the same shape Interval and Timeout already
have for timers.
Inside if (__DEV__) the hand-rolled call is right, and this asks only that it be cleaned
up. A decorator is code on the class, so no guard can remove it and a dev-only listener
written with @onWindow would attach in production too. Keep the raw call there and pair it
with a removeEventListener for the same event in @destroyed, with the same handler
reference. { once: true } and { signal } close it as well, and neither is reported.
A listener on anything else is not this rule's business: an AbortSignal dies with its
request and an element with the element, and no decorator covers either.
Next
- All rules — the other checks this one runs beside.
- Checking your app — how to run it, and what it proves.