Notes on TypeScript and CustomEvent
Quick notes on using CustomEvent with TypeScipt@5.3.3.
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.