Search code examples
reactjstypescriptnext.jsdefinitelytyped

How to use the EventListener Type in React?


React defines some (not all) DOM types in a file called global.d.ts, which is causing problems. There seems to be a conflict when using the EventListener type (coming from the TypeScript lib dom) in a React project, because most other types are declared by React.

Is there any way of using the EventListener type in a React project? I am using next.js, so possibly it is not a good idea to discard the global.d.ts file using exclude in the tsconfig.json for SSR to work?

What is expected (pure TS):
https://codesandbox.io/s/nice-tdd-xnkov?file=/src/index.ts

window.onload = () => {
  // NO Problem: Listener is correct.

  // Explanation:
  // EventListener is coming from typescript (lib.dom.d.ts)
  // MouseEvent is coming from typescript (lib.dom.d.ts)
  const listener: EventListener = (ev: MouseEvent) => {
    console.log("Click!");
  };
  document.body.addEventListener("click", listener);
};

Does not work in React:
https://codesandbox.io/s/react-dom-type-conflict-1y06r?file=/src/App.tsx

useEffect(() => {
  // Problem: Listener is seemingly incorrect.

  // Explanation:
  // EventListener is coming from typescript (lib.dom.d.ts)
  // MouseEvent is coming from react (global.d.ts)
  const listener: EventListener = (ev: MouseEvent) => {
    console.log("Click!");
  };
  document.body.addEventListener("click", listener);
}, []);

The explanation, why React is declaring these types (from global.d.ts):

React projects that don't include the DOM library need these interfaces to compile. React Native applications use React, but there is no DOM available. The JavaScript runtime is ES6/ES2015 only. These definitions allow such projects to compile with only --lib ES6.

Warning: all of these interfaces are empty. If you want type definitions for various properties (such as HTMLInputElement.prototype.value), you need to add --lib DOM (via command line or tsconfig.json).


Solution

  • Edit: As @Christoph Bühler pointed out it appears that addEventListener event handler receives a MouseEventInit instead of MouseEvent

    So this works:

     const listener: EventListener = (ev: MouseEventInit) => {
       console.log("Click!");
    
       const e = ev as MouseEvent;
       console.log(e.target);
     };
    

    Working CodeSandbox that I forked from yours.