\nhttps://codesandbox.io/s/nice-tdd-xnkov?file=/src/index.ts
\nwindow.onload = () => {\n // NO Problem: Listener is correct.\n\n // Explanation:\n // EventListener is coming from typescript (lib.dom.d.ts)\n // MouseEvent is coming from typescript (lib.dom.d.ts)\n const listener: EventListener = (ev: MouseEvent) => {\n console.log("Click!");\n };\n document.body.addEventListener("click", listener);\n};\n
\nDoes not work in React:
\nhttps://codesandbox.io/s/react-dom-type-conflict-1y06r?file=/src/App.tsx
useEffect(() => {\n // Problem: Listener is seemingly incorrect.\n\n // Explanation:\n // EventListener is coming from typescript (lib.dom.d.ts)\n // MouseEvent is coming from react (global.d.ts)\n const listener: EventListener = (ev: MouseEvent) => {\n console.log("Click!");\n };\n document.body.addEventListener("click", listener);\n}, []);\n
\nThe explanation, why React is declaring these types (from global.d.ts
):
\n\n","author":{"@type":"Person","name":"Christoph Bühler"},"upvoteCount":2,"answerCount":2,"acceptedAnswer":{"@type":"Answer","text":"React projects that don't include the DOM library need these\ninterfaces to compile. React Native applications use React, but there\nis no DOM available. The JavaScript runtime is ES6/ES2015 only. These\ndefinitions allow such projects to compile with only
\n--lib ES6
.Warning: all of these interfaces are empty. If you want type\ndefinitions for various properties (such as\nHTMLInputElement.prototype.value), you need to add
\n--lib DOM
(via\ncommand line or tsconfig.json).
Edit: As @Christoph Bühler pointed out it appears that addEventListener event handler receives a MouseEventInit
instead of MouseEvent
So this works:
\n const listener: EventListener = (ev: MouseEventInit) => {\n console.log("Click!");\n\n const e = ev as MouseEvent;\n console.log(e.target);\n };\n
\nWorking CodeSandbox that I forked from yours.
\n","author":{"@type":"Person","name":"whygee"},"upvoteCount":3}}}Reputation: 2923
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).
Upvotes: 2
Views: 2016
Reputation: 11
You can use a triple-slash directive.
I had a similar issue in a React + TypeScript project, where the linter (eslint-typescript
) marked several breaches of the no-unsafe-call
and
no-unsafe-member-access
rules, when using the DOM API. For example, document.getElementById("myId")
would lead to these two errors:
ESLint: Unsafe call of an
any
typed value. (@typescript-eslint/no-unsafe-call)
ESLint: Unsafe member access .getElementById on an
any
value. (@typescript-eslint/no-unsafe-member-access)
Like @Bugbeeb and @Christoph Bühler, I think that this happens because the empty type declarations from React somehow override TypeScript's internal "lib.dom.d.ts" type definitions.
To fix this, I used a triple-slash directive (see https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-lib-). Insert ///<reference lib="dom"/>
in the first line of the file, like so:
///<reference lib="dom"/>
// Your code
document.getElementById("myId")
// ...
With this line, TypeScript explicitly includes the built-in lib file, and should prioritize their own type declarations over React's.
N.b.: In the project I work on, I only had to do this in a single file, which fixed the issue in other files as well.
Upvotes: 1
Reputation: 1984
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.
Upvotes: 3