52

I have this custom event setup, and it works with TypeScript 2.5.3, but when I updated to 2.6.1 I get an error

window.addEventListener('OnRewards', (e: CustomEvent) => {
    // my code here
})

[ts] Argument of type '(e: CustomEvent) => void' is not assignable to parameter of type 'EventListenerOrEventListenerObject'.

Type '(e: CustomEvent) => void' is not assignable to type 'EventListenerObject'.

Property 'handleEvent' is missing in type '(e: CustomEvent) => void'.

I am not exactly sure what to do here to fix this.

4
  • What is CustomEvent? Maybe try with e: Event? Commented Nov 7, 2017 at 20:03
  • CustomEvent is var event = new CustomEvent('OnRewards', { detail: data }); Commented Nov 7, 2017 at 20:07
  • If I use Event then it doesn't know what detail is inside of the function Commented Nov 7, 2017 at 20:09
  • Maybe they introduced a new bug in the latest version? Commented Nov 7, 2017 at 20:10

7 Answers 7

70

This is due to the behavior of the --strictFunctionTypes compiler flag added in TypeScript v2.6. A function of type (e: CustomEvent) => void is no longer considered to be a valid instance of EventListener, which takes an Event parameter, not a CustomEvent.

So one way to fix it is to turn off --strictFunctionTypes (but don't do that, it's not safe).


Another way is to pass in a function that takes an Event and then narrows to CustomEvent via a type guard:

function isCustomEvent(event: Event): event is CustomEvent {
  return 'detail' in event;
}

window.addEventListener('OnRewards', (e: Event) => {
  if (!isCustomEvent(e))
    throw new Error('not a custom event');
  // e is now narrowed to CustomEvent ...
  // my code here 
})

A third way is to use the other overload of addEventListener():

addEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, useCapture?: boolean): void;

If the type parameter is the name of a known event type (K extends keyof WindowEventMap) like "onclick", then the listener function will expect its parameter to be of that narrowed event type (WindowEventMap[K]). The problem is that "OnRewards" is not a known event type... unless you use declaration merging to make it known:

// merge into WindowEventMap
interface WindowEventMap {
    OnRewards: CustomEvent
}

Or, if you're inside a module (anything with export in it), use global augmentation:

// merge into WindowEventMap
declare global {
  interface WindowEventMap {
    OnRewards: CustomEvent
  }
}

Then use your code as before:

// no error!
window.addEventListener('OnRewards', (e: CustomEvent) => {
    // my code here
})

So, those are your options. Which one you want to choose is up to you. Hope that helps; good luck!

7
  • I was able to get the second way to work, however, I like the way of the 3rd option the best. It didn't seem to work though, as I was still getting the same error message. Commented Nov 8, 2017 at 14:27
  • 1
    Great, just edited the answer in case anyone else runs into that problem.
    – jcalz
    Commented Nov 8, 2017 at 14:42
  • 23
    +1, but I really wish you didn't start with turn off --strictFunctionTypes. So many TS questions are answered with "disable the checks", and if that's the standard choice, then we could might as well just drop TS.
    – Letharion
    Commented Feb 21, 2018 at 17:28
  • 1
    instead of type guard function can we check with if(!(event instanceof CustomEvent)) throw 'not a CustomEvent'?
    – Yukulélé
    Commented Oct 21, 2020 at 9:49
  • 1
    If you use document.addEventListener instead of window.addEventListener, then you need to use DocumentEventMap instead of WindowEventMap. Commented Jan 2, 2022 at 19:25
20

Building off of jcalz's excellent answer, you can also use a type assertion to be a little cleaner:

window.addEventListener('OnRewards', (e: Event) => {
    const detail = (<CustomEvent>e).detail;
    ...
});
0
4

I'm not positive why all of the answers here are so convoluted. Simple typecasting can solve this:

window.addEventListener('OnRewards', (e: Event) => {
    const detail = (e as CustomEvent).detail
})
3

You also have this option:

window.addEventListener('OnRewards', (e: CustomEvent) => {
    // your code here
} as (e: Event) => void)
1

I created a generic function based off @jcalz's answer

/**
 * Checks whether an object can be safely cast to its child type
 * @param parent the object to be 'narrowly' cast down to its child type
 * @param checkForProps props which aught to be present on the child type
 */
export function isSubTypeWithProps<P, C extends P>(parent: P, ...checkForProps: (keyof C)[]): parent is C {
  return checkForProps.every(prop => prop in parent);
}

/**
 * Usage example
 */
const el = document.getElementById('test');
el.addEventListener('click', (e: Event) => {
  if (isSubTypeWithProps<Event, MouseEvent>(e, 'which')) {
    if (e.which === 1) { // primary mouse button only ('which' prop is only available on MouseEvent)
      console.log('clicked');
    }
  }
});
1

I was having this problem with the "click" event which was unexpected because it's a native event, turns out you cannot just use document.querySelector inlined because the inferred type is Element | null, not HTMLElement | null, so you have to declare it to be an HTMLElement, meaning this fails:

const ele = document.querySelector('#ele');
ele?.addEventListener('click', (event: MouseEvent) => {
   // ...
});

But it works fine when you declare it as an HTMLElement:

const ele = document.querySelector('#ele') as HTMLElement | null;
ele?.addEventListener('click', (event: MouseEvent) => {
    // ...
});

I know this question is about a custom event but this is the first result when searching for the same error but on the "click" event.

1
  • 1
    just to add that querySelector is generic and can accept an explicit type parameter. In this case, querySelector<HTMLElement> returns HTMLElement | null
    – superjos
    Commented Feb 15, 2023 at 1:20
0

yes and another way to satisfy your compiler is to cast the event object eg, yourFuctionName(e: Event){ const event = e as CustomEvent <SegmentChangeEventDetail>; console.log(event.detail)}

Not the answer you're looking for? Browse other questions tagged or ask your own question.