You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
TypeScript doesn't allow event : CustomEvent in addEventListener #28357
The problem is that the CustomEvent type gives me the error shown below. If I replace CustomEvent with Event, then there is no error, but then I have difficulty getting event.detail out of the event listener.
"resource": "/c:/Users/me/Documents/app/file.ts",
"owner": "typescript",
"code": "2345",
"severity": 8,
"message": "Argument of type '(event: CustomEvent<any>) => void' is not assignable to parameter of type 'EventListenerOrEventListenerObject'.\n Type '(event: CustomEvent<any>) => void' is not assignable to type 'EventListener'.\n Types of parameters 'event' and 'evt' are incompatible.\n Type 'Event' is not assignable to type 'CustomEvent<any>'.\n Property 'detail' is missing in type 'Event'.",
"source": "ts",
"startLineNumber": 86,
"startColumn": 44,
"endLineNumber": 86,
"endColumn": 72
How is the addEventListener defined in your version of TypeScript? and how would I update it on my PC if it is a dependency of Stencil. In my package.json, I just have the following devDependencies defined, note TypeScript is not listed anywhere:
Reacted by Marek Krzeminski, Nicholas Westbrook, Mohammed Zaghloul, Greg Woods, Yash Kadaru, Daniil Chmylov, Łukasz, Brad.Z, Conan06, Jonathan Phillips and 189 more
Reacted by Greg Woods, Saninn Salas Diaz, RV, Jozef Cipa, Christopher Murphy, Joe Souter, Lucas Forster, Guardian of the Spring, ferikeem, Andrey Briginets and 32 more
Reacted by nbili, Tamas, Sulhadin Öney, Sukka, Hiroki Minagawa, TigerMehMat, Ann Kilzer キルザー杏, Daniel Lizik, Alex Deweert, Dinesh Swami and 24 more
Reacted by Daniel Lizik, Alex Deweert, Dinesh Swami, Cole Crouter, Vishu, Miklos Nemeth, Ben Lofo, Alexey, Nicholas Haley, Yein Ahn and 5 more
strictFunctionTypes causes this issue. Try strictFunctionTypes: false on tsconfig.
Reacted by Marek Krzeminski, Prashant, Yisheng Jiang, chengxiao, Benmarouane Amine , Virendraccc, Remzi Atay and oliver
Reacted by Łukasz, Raul Tambre, David Sorrentino, Tony Gentilcore, Mischa Spiegelmock, Zaydek, Wa il H, Guilherme Ribeiro, Sergey Kozlov, Fred and 93 more
What is the proper solution to this - or what is the reason why it creates an error in the first place?
I have been using msheakoski's solution. It is verbose but works. Ideally the EventListenerOrEventListenerObject would be updated to include CustomEventListener
Reacted by Nico Jansen, Jon Rimmer, Clément P, Raul Tambre, Sergey Kozlov, Christopher Regner, Ali Mihandoost (علی میهندوست), brunnerh, Vít Rozsíval, a11delavar and 11 more
genEventTrigger(param: any){return(event: Event)=>{// const someVar = this.variableFromTheScopeOfTheFunction; <- can't do this because, 'this' here will refer to the document, not the scope of the function itselfconstsomeVar=param;// have to do this INSTEAD, so it's set during compile time// do things here};}
At this point, Typescript complains that the function signature is invalid for an EventListener.
Adding as EventListener, fixes it:
['click','touchend'].forEach(handler=>document.addEventListener(handler,this.genEventTrigger('a compile time parameter')asEventListener));
It's quite silly. Unless I could be doing something better, feel free to correct me!
Reacted by Eric Richards and Javier Perez
Reacted by Marek Krzeminski, Julien Viala, Alex Koppel, shiny-mountain and Mahdi Farahani
I use an other workaround which keep a type guard in the addEvenListener.
Because the interface of EventListener is contravariance rules we need to check if the event receive in our addEvenlistener contains the params detail.
For that, you could define an utils function like that
function isCustomEvent(evt: Event): evt is CustomEvent {
return (evt as CustomEvent).detail !== undefined;
}
Reacted by Marek Krzeminski, Mateusz Chrzonstowski, Levi Wheatcroft, Arnau Sanchez Sala, Will Griffiths, slevy85 and Nik Paro
Reacted by Carlos Jesús Huchim Ahumada and Jules Hablot
I also have this issue - I want to define a subclass of Event that has custom fields on it. If there was a generic type argument for EventListener maybe that would help?
Reacted by Enoh Barbu, Matthew Tylee Atkinson, Michael Jonker, Cédric Belin, Hunter Loftis, Alex Michaud, Russell Vea, lbineau, satoshionoda and Wasuwat Limsuparhat
I found this thread while intending to file a new bug report.
Agreed Thomas Riffard (@ferdodo) , this thread is still relevant and has only workarounds but no recognition of this as a bug since 2018.
Would an admin maybe tag this thread as a bug?
Should I create a new issue tagged as a bug?
Here is the problem illustrated on the typescript playground. Typescript v4.7.4
It is persisting into 4.8Beta and Nightly.
Reacted by Emily Chen and Seb Ringrose
Reacted by Jesse Gavin, Alex Vipond, Lukas Bombach, Emily Chen and Tim Reichen
I regard this as a bug. MDN states that you can dispatch a CustomEvents to EventTargets, yet TypeScript does not recognize this. Compare EventTarget on MDN
I also want to report that this is definitely a bug in my opinion. Here I want to supply a listener that is designed to receive CustomEvents and then apply the listener function to ancestor nodes in a tree of event targets:
addBranchEventListener(type: string, listener: (e: CustomEvent) => void) {
this.addEventListener(type, (e) => listener(e as CustomEvent))
if (this.parent) this.parent.addBranchEventListener(type, listener)
}
It would be nice to not need the type assertion here.
This thread has several suggestions to extend EventMap interfaces to define your own custom event. I think that's the way to go, because otherwise things may break whenever HTML define a new event, which would then suddenly fire non-custom events.
...and if people get to this in the issue, please consider that it is a much better TypeScript pattern to define your types in the object itself, rather than in the callback, which avoids some of the issues that many are having here.
Similar to how you'd extend the Window (globalThis) object, the same logic can be applied here via the WindowEventMap within a definition file:
interfaceExample{foo: string;bar: string;}declare global {interfaceWindowEventMap{'custom:event-1': CustomEvent<Example>;'custom:event-2': CustomEvent<Example>;}}
Reacted by Esa-Matti Suuronen, Lưu Xuân Trường, rbnts, Alec Bassingthwaighte, Cristian Necula, Martin Petrov, Joakim Hedlund, Junior, Илья, Jose Miguel Gallas Olmedo and 2 more
Reacted by Robin Schambach, Nitzan, Silver, Paul Singh, Carlos Rosquillas, Zachary J. Rollyson, gabriel-peracio, Abhijith Vijayan [KUBRIC], Alec Bassingthwaighte, StEve YoUng and 5 more
import{useAppDispatch}from'@/app/hooks';import{useCallback}from'react';import{ConfirmationDialogState,closeConfirmationDialog,openConfirmationDialog,CustomConfirmationEvent,CustomConfirmationEventType,YesNoCancel}from'@/store/confirm'constuseConfirmationDialog=()=>{constdispatch=useAppDispatch()constshowConfirmationDialog=useCallback((props: ConfirmationDialogState)=>{returnnewPromise<YesNoCancel>((resolve)=>{consthandleConfirm:EventListener=(event: CustomConfirmationEvent)=>{//this.[...] // this is Document//event.detail ... //is your CustomParams type.//console.log(JSON.stringify(event.detail?.message),event.type) //"YES" confirmationDialogConfirm dispatch(closeConfirmationDialog())resolve(event.detail?.messageasYesNoCancel)};dispatch(openConfirmationDialog(props))// Підписуємося на події підтвердження та скасуванняdocument.addEventListener(CustomConfirmationEventType,handleConfirm,{once: true})// Функція очищенняreturn()=>{document.removeEventListener(CustomConfirmationEventType,handleConfirm)};});},[dispatch]);return{ showConfirmationDialog };};exportdefaultuseConfirmationDialog;
const{showConfirmationDialog}=useConfirmationDialog()consthandleLogout=()=>{showConfirmationDialog({isOpenConfirm: true,confirmLabel: 'Вихід с кабінету',innerText: 'Бажаєте вийти з кабінету?\n(Буде очищено кеш та сховище браузера від даних сайту)',confirmYesLabel: 'Так, вийти',confirmNoLabel: 'Ні, залишитись',}).then((value)=>{switch(value){caseYesNoCancel.YES: {dispatch(runLogout(navigate));return;}caseYesNoCancel.NO: {dispatch(setMessage({message:'З поверненням 😘',open:true}));return;}caseYesNoCancel.CANCEL: {return;}}}).catch(()=>{null})}
Following up on Wes Bos (@wesbos), this is how I solve it for dispatching as well:
/** * Type-safe event listener and dispatch signatures for the custom events * defined in `TDetails`. */exporttypeCustomEventTarget<TDetails>={addEventListener<TTypeextendskeyofTDetails>(type: TType,// eslint-disable-next-line @typescript-eslint/no-explicit-anylistener: (ev: CustomEvent<TDetails[TType]>)=>any,options?: boolean|AddEventListenerOptions,): void;removeEventListener<TTypeextendskeyofTDetails>(type: TType,// eslint-disable-next-line @typescript-eslint/no-explicit-anylistener: (ev: CustomEvent<TDetails[TType]>)=>any,options?: boolean|EventListenerOptions,): void;dispatchEvent<TTypeextendskeyofTDetails>(ev: _TypedCustomEvent<TDetails,TType>,): void;};/** * Extends an element type with type-safe event listener signatures for the * custom events defined in `TDetails`. */exporttypeCustomEventElement<TDetails,TElement=HTMLElement,>=CustomEventTarget<TDetails>&TElement;/** * Internal declaration for the `typeof` trick below. * Never actually implemented. */declareclass_TypedCustomEvent<TDetails,TTypeextendskeyofTDetails,>extendsCustomEvent<TDetails[TType]>{constructor(type: TType,eventInitDict: {detail: TDetails[TType]}&EventInit,);}/** * Typed custom event (technically a typed alias of `CustomEvent`). * Use with `CustomEventTarget.dispatchEvent` to infer `detail` types * automatically. */exportconstTypedCustomEvent=CustomEventastypeof_TypedCustomEvent;
With that setup you can do:
// Map of event type -> detailinterfaceUserEvents{'user:updated': {name: string;age: number};}constdiv: CustomEventElement<UserEvents>=document.createElement('div');div.addEventListener('user:updated',(e)=>{// e.detail is inferred to { name: string; age: number };});div.dispatchEvent(newTypedCustomEvent('user:updated',{// detail is inferred to { name: string; age: number };detail: {name: 'Bob',age: 42},}),);
It has zero runtime overhead (TypedCustomEvent is really just CustomEvent) and is fully type-safe, I think. Not sure if the declaration "trick" is actually necessary, maybe it can be done more succinctly :)
Activity
mjbvz commentedon Nov 6, 2018
I can't repo this with the TypeScript 3.1.4:
tsconfig.json?mmakrzem commentedon Nov 6, 2018
I'm writing my code using https://stenciljs.com/ which is reporting the following TypeScript version:
My tsconfig.json file looks like this:
mmakrzem commentedon Nov 6, 2018
In the lib.dom.d.ts file I have the following definition for HTMLElement's addEventListener:
where:
declare type EventListenerOrEventListenerObject = EventListener | EventListenerObject;and
How is the addEventListener defined in your version of TypeScript? and how would I update it on my PC if it is a dependency of Stencil. In my package.json, I just have the following devDependencies defined, note TypeScript is not listed anywhere:
msheakoski commentedon Nov 7, 2018
I have always needed to write it like this to avoid the issue with custom events:
saschanaz commentedon Nov 8, 2018
strictFunctionTypescauses this issue. TrystrictFunctionTypes: falseon tsconfig.essenmitsosse commentedon Feb 6, 2019
What is the proper solution to this - or what is the reason why it creates an error in the first place?
mmakrzem commentedon Feb 6, 2019
I have been using msheakoski's solution. It is verbose but works. Ideally the EventListenerOrEventListenerObject would be updated to include CustomEventListener
yashsway commentedon Apr 10, 2019
I can confirm that I still have to do this. Right now, I have something like this:
At this point, Typescript complains that the function signature is invalid for an EventListener.
Adding
as EventListener, fixes it:It's quite silly. Unless I could be doing something better, feel free to correct me!
Nikohelie commentedon Jul 29, 2019
I use an other workaround which keep a type guard in the addEvenListener.
Because the interface of EventListener is contravariance rules we need to check if the event receive in our addEvenlistener contains the params detail.
For that, you could define an utils function like that
function isCustomEvent(evt: Event): evt is CustomEvent {
return (evt as CustomEvent).detail !== undefined;
}
revmischa commentedon Aug 1, 2019
I also have this issue - I want to define a subclass of Event that has custom fields on it. If there was a generic type argument for EventListener maybe that would help?
currently I have to do:
something like this might work?
jamie-pate commentedon Sep 16, 2019
lib.dom.d.tsalso uses this definition if it helps you define your signatures:so, for instance if you want a key/value map of events in an object:
23 remaining items
greggman commentedon Mar 18, 2022
Okay, I think I worked it out
ferdodo commentedon Apr 9, 2022
This issue is still relevant, there are only workarounds in this thread to achieve this:
citkane commentedon Aug 17, 2022
I found this thread while intending to file a new bug report.
Agreed Thomas Riffard (@ferdodo) , this thread is still relevant and has only workarounds but no recognition of this as a bug since 2018.
Would an admin maybe tag this thread as a bug?
Should I create a new issue tagged as a bug?
Here is the problem illustrated on the typescript playground. Typescript v4.7.4
It is persisting into 4.8Beta and Nightly.
LukasBombach commentedon Sep 28, 2022
I regard this as a bug. MDN states that you can dispatch a
CustomEvents toEventTargets, yet TypeScript does not recognize this. Compare EventTarget on MDNThis works on all modern browsers.
The interface is typed as
lib.dom.tsThat is a mismatch. It should be
and
What's curious though is that the WHATWG specs do not reflect this:
https://dom.spec.whatwg.org/#interface-eventtarget
Maybe this is the reason / root cause?
ChristophP commentedon Jan 1, 2023
I'm seeing this issue as well. This bug seems to have existed for quite a while now.
sejori commentedon Jan 19, 2023
I also want to report that this is definitely a bug in my opinion. Here I want to supply a listener that is designed to receive
CustomEventsand then apply the listener function to ancestor nodes in a tree of event targets:It would be nice to not need the type assertion here.
saschanaz commentedon Jan 19, 2023
This thread has several suggestions to extend EventMap interfaces to define your own custom event. I think that's the way to go, because otherwise things may break whenever HTML define a new event, which would then suddenly fire non-custom events.
Edit: See also microsoft/TypeScript-DOM-lib-generator#1535 (comment).
mkcode commentedon Apr 1, 2023
Chiming in to say that this bug is still at large!Lukas Bombach (@LukasBombach) describes how this should be fixed: #28357 (comment)I no longer thing this a bug. See here
mkcode commentedon Apr 1, 2023
...and if people get to this in the issue, please consider that it is a much better TypeScript pattern to define your types in the object itself, rather than in the callback, which avoids some of the issues that many are having here.
Kagami Sascha Rosylight (@saschanaz) pointed out that these methods should be used
wesbos commentedon Apr 24, 2023
Here is an example based on the above code. Still noodling on how to best approach the dispachEvents inferrance
panoply commentedon May 24, 2023
Adding this here for the sake of brevity.
Similar to how you'd extend the
Window(globalThis) object, the same logic can be applied here via theWindowEventMapwithin a definition file:mp3por commentedon Mar 20, 2024
FYI this is how I did it without any additional changes - I just make sure I type the handler correctly and it works
commanderz commentedon Jun 29, 2024
Confirm that Custom Events working in typescript without custom dom.d.ts file, just use it like this:
My types:
My custom hook useConfirmationDialog.ts:
code in ConfirmationDialog.tsx:
and hook useConfirmationDialog used like this:
morris commentedon Aug 26, 2024
Following up on Wes Bos (@wesbos), this is how I solve it for dispatching as well:
With that setup you can do:
It has zero runtime overhead (
TypedCustomEventis really justCustomEvent) and is fully type-safe, I think. Not sure if the declaration "trick" is actually necessary, maybe it can be done more succinctly :)TangoPJ commentedon Apr 4, 2025
I solved this problem in the following way: