Quick notes on using CustomEvent with
[email protected]
.
The Issue: No overload matches this call…
In case you tried to use a
CustomEvent
with TypeScript, chances are you experienced the
No overload matches this call.
error.
No overload matches this call.
Overload 1 of 2, '(type: keyof DocumentEventMap, listener: (this: Document, ev: Event | UIEvent | AnimationEvent | MouseEvent | InputEvent | ... 13 more ... | WheelEvent) => any, options?: boolean | ... 1 more ... | undefined): void', gave the following error.
Argument of type '"custom:event:name"' is not assignable to parameter of type 'keyof DocumentEventMap'.
Overload 2 of 2, '(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | undefined): void', gave the following error.
Argument of type '(e: CustomEvent<string>) => void' is not assignable to parameter of type 'EventListenerOrEventListenerObject'.
Type '(e: CustomEvent<string>) => void' is not assignable to type 'EventListener'.
Types of parameters 'e' and 'evt' are incompatible.
Type 'Event' is missing the following properties from type 'CustomEvent<string>': detail, initCustomEvent(2769)
The error happens because in
dom.generated.d.ts
we have the
EventListenerOrEventListenerObject
which doesn’t support
CustomEvents
.
// https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts#L8150
interface EventListener {
(evt: Event): void;
interface EventListenerObject {
handleEvent(object: Event): void;
type EventListenerOrEventListenerObject = EventListener | EventListenerObject;
// https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts#L8199
addEventListener<K extends keyof AbortSignalEventMap>(type: K, listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
The Quick and Dirty
// Register event
const callback = (e: CustomEvent<string>) => console.log(e.detail);
document.addEventListener("custom:event:name", callback as EventListener);
// Alternative syntax inline
document.addEventListener("custom:event:name", ((e: CustomEvent<string>) => console.log(e.detail)) as EventListener);
// Fire event
const ev = new CustomEvent("custom:event:name", {detail: "yadda"});
document.dispatchEvent(ev);
The trick is in casting the
callback
in a
EventListener
even if we’re using
CustomEvent
instead of
Event
.
Extending EventTarget
As alternative, you can extend
EventTarget
which is the object that expose
addEventListener
,
removeEventListener
, and
dispatchEvent
.
The issue with this approach is that you can’t leverage
document
global scope, which possibly was the reason you tried to used it in the first place.
interface YourEventMap {
"yourEvent": CustomEvent
interface YaddaEventTarget extends EventTarget {
addEventListener<K extends keyof YourEventMap>(event: K, listener: ((this: Yadda, ev: YourEventMap[K]) => any) | null, options?: AddEventListenerOptions | boolean): void;
addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void;
class YaddaEventTarget extends EventTarget {
const instance = new YaddaEventTarget();
instance.addEventListener("yourEvent", (event: CustomEvent) => console.log(event));
instance.dispatchEvent(new CustomEvent("yourEvent", {detail: "bla"}));
Current status of the issue
As per Feb 2024, I see the
issue on github
getting bigger and bigger, with
the discussion about the fix in stall
so i guess that for a while this will stay relevant.
Links
TypeScript doesn't allow event : CustomEvent in addEventListener
The possible solution
Make EventListener covariant
Supporting lib from node_modules