TypeScript erroneously defines data type

Here is a snippet of code that I am working with:

interface Ev<K extends keyof WindowEventMap> {
  readonly name: K;
  readonly once?: boolean;
  readonly callback: (ev: WindowEventMap[K]) => void;
}

function createEventListener<K extends keyof WindowEventMap>({ name, once = false, callback }: Ev<K>): Ev<K> {
  return { name, once, callback };
}

const clickEvent = createEventListener({
  name: "click",
  callback: (ev) => console.log(ev),
});

const scrollEvent = createEventListener({
  name: "scroll",
  callback: (ev) => console.log(ev),
});

const events = [clickEvent, scrollEvent];

for (const event of events) {
  document.addEventListener(event.name, (ev) => event.callback(ev), { once: event.once });
}

In the end of this script, I have a loop set up to access the callback function of each event. However, there seems to be an issue where I cannot pass the correct parameters because TypeScript has compiled them incorrectly.

This is what TypeScript is currently doing:

(property) Ev<K extends keyof WindowEventMap>.callback: (ev: MouseEvent) => void

What I actually need is:

(property) Ev<K extends keyof WindowEventMap>.callback: (ev: MouseEvent | Event) => void

TypeScript Playground

Answer №1

Let's streamline the discussion by switching your scroll event to a keyup event since it is of type KeyboardEvent and does not fall into the broader category of Event, which could potentially overlap or conflict with MouseEvent:

const clickEvent = createEventListener({
  name: "click",
  callback: (ev: MouseEvent) => console.log(ev),
});

const keyupEvent = createEventListener({
  name: "keyup",
  callback: (ev: KeyboardEvent) => console.log(ev),
});
const events = [clickEvent, keyupEvent];

When choosing an event from events, the compiler deduces that the name property consists of a union of the string literal types "click" and "keyup". Similarly, it recognizes that the callback property is a union of function types (ev: MouseEvent) => void and (ev: KeyboardEvent) => void:

for (const event of events) {

  event.name // "click" | "keyup"
  event.callback // ((ev: MouseEvent) => void) | ((ev: KeyboardEvent) => void)

These inferred types are indeed accurate. It confirms that event.name will be either "click" or "keyup", and event.callback will expect a function parameter of either MouseEvent or KeyboardEvent.

Attempting to invoke event.callback, which involves a union of function types, necessitates passing in a parameter that represents an intersection of both function's parameter types. In other words, calling event.callback(x) demands that x conforms to MouseEvent & KeyboardEvent, indicating that it embodies characteristics of both a MouseEvent and a KeyboardEvent:

declare const m: MouseEvent;
event.callback(m); // error, should not be 'MouseEvent & KeyboardEvent'.
declare const k: KeyboardEvent;
event.callback(k); // error, should not be 'MouseEvent & KeyboardEvent'.
declare const both: MouseEvent & KeyboardEvent;
event.callback(both) // okay

This concept of unions of functions that require intersections of parameters is a unique aspect of TypeScript. It maintains correctness and type safety. When faced with selecting between one function demanding a MouseEvent and another needing a KeyboardEvent, providing an input that satisfies both requirements like MouseEvent & KeyboardEvent becomes crucial.

Hence, inferring event.callback as

(ev: MouseEvent | KeyboardEvent) => void
would be inaccurate. The compiler's decision here is entirely correct, even though the desired type may appear beneficial down the line.


[... continue with the remaining content ...]

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How can you resolve the error message "No overload matches this call." while implementing passport.serializeUser()?

Currently, I am working on implementing passport.serializeUser using TypeScript. passport.serializeUser((user: User, done) => { done(null, user.id) }); The issue I am encountering is as follows: No overload matches this call. Overload 1 of 2, &ap ...

When using Styled Components with TypeScript, you may encounter the error message "No overload matches

Recently, I've delved into Style Components in my journey to create a weather app using React Native. In the past, I would typically resort to CSS modules for styling, but it appears that this approach is not feasible for mobile development. Below is ...

Enabling the "allowUnreachableCode" Compiler Option in Visual Studio 2015 Triggers "JsErrorScriptException (0x3001)" Issue

We've implemented TypeScript in our Visual Studio 2015 (Update 2) setup for a non-ASP.Net project consisting of pure HTML and JavaScript. In order to disable the 'allowUnreachableCode' option, we made adjustments in the tsconfig file. It&apo ...

What is the best way to prevent the output folder from appearing in the import statements for users of my package?

I have a project written in Typescript that consists of multiple .d.ts files. I would like to package this project as an npm module and utilize it in another project. In the second project, my goal is to be able to import modules like so: import {Foo} fr ...

The error message thrown is: "Unable to assign value to property 'formGroup' as it is not defined

There's a particular component structure as shown below: import { Component, Input } from '@angular/core'; import { WorkingHours } from '../../../../../hours'; import { FormGroup } from '@angular/forms'; @Component({ ...

How can we effectively divide NGXS state into manageable sections while still allowing them to interact seamlessly with one another?

Background of the inquiry: I am in the process of developing a web assistant for the popular party game Mafia, and my objective is to store each individual game using NGXS. The GitLab repository for this project can be found here. The game includes the f ...

What strategy does Node recommend for organizing code into multiple files?

In the midst of my current project, which involves NodeJS and Typescript, I am developing an HTML5 client that communicates with a NodeJS server via web-sockets. With a background in C#, I prefer to organize my code into separate files for different functi ...

Tips for minimizing delay after user input with Material UI

Background I'm currently working on a website project that includes a carousel of MUI cards using a unique stack as the underlying component. However, I've encountered an issue where there is a noticeable 4-second delay whenever I try to scroll ...

Encountering a 403 error when attempting to upload files to Google Cloud Storage (GCS) using Signed URLs

The main aim is to create a signed URL in the api/fileupload.js file for uploading the file to GCS. Then, retrieve the signed URL from the Nextjs server through the nextjs API at localhost://3000/api/fileupload. Finally, use the generated signed URL to upl ...

Creating instance methods in a TypeScript object can be accomplished by defining the methods within the object's class definition. When the object is

As a seasoned Java developer, I've recently been dabbling in TypeScript. Let me introduce you to my user object: export class User { id: string; name: string; email?: string; unit: string; street: string; postalcode: string; ...

When running `aws-cdk yarn synth -o /tmp/artifacts`, an error is thrown stating "ENOENT: no such file or directory, open '/tmp/artifacts/manifest.json'"

Starting a new aws-cdk project with the structure outlined below src └── cdk ├── config ├── index.ts ├── pipeline.ts └── stacks node_modules cdk.json package.json The package.json file looks like this: " ...

What is the best way to insert CSS code into a custom Vue directive file?

I need a solution that applies a gradient background to the parent div in case an image fails to load. I've attempted to create a directive for this purpose: export default { bind(el: any, binding: any) { try { ..... ...

Set an enumerated data type as the key's value in an object structure

Here is an example of my custom Enum: export enum MyCustomEnum { Item1 = 'Item 1', Item2 = 'Item 2', Item3 = 'Item 3', Item4 = 'Item 4', Item5 = 'Item 5', } I am trying to define a type for the f ...

Angular 7 router navigate encountering a matching issue

I created a router module with the following configuration: RouterModule.forRoot([ {path: 'general', component: MapComponent}, {path: 'general/:id', component: MapComponent}, {path: '', component: LoginComponent} ]) Sub ...

Solving the Angular 11 issue of undefined array mapping

When using an API to retrieve a response array, I encountered an issue trying to map the "id" under the "quiz_records" array as it kept returning undefined. Despite believing that my code was correct, I couldn't figure out the problem. Here is the sn ...

Handling Errors in RXJS Angular - Utilizing .subscribe and Observable Strategy

For creating a new product using a backend API, the Angular frontend code needs to make a call to the API. I am interested in learning how to implement error handling with the use of .subscribe method. Currently, I am utilizing HTTPClient along with Observ ...

Experiment with Observable in fakeAsync, applying a delay and tick functions through Jasmine testing framework

I am currently working with a pipe that assists in returning the state of an observable. import {Pipe, PipeTransform} from '@angular/core'; import {Observable, of} from 'rxjs'; import {catchError, map, startWith} from 'rxjs/operato ...

Prisma auto-generating types that were not declared in my code

When working with a many-to-many relationship between Post and Upload in Prisma, I encountered an issue where Prisma was assigning the type 'never' to upload.posts. This prevented me from querying the relationship I needed. It seems unclear why P ...

Combining Bazel, Angular, and SocketIO Led to: Unforeseen Error - XMLHttpRequest Not Recognized as Constructor

I am looking to integrate ngx-socket-io into my Angular application. I utilize Bazel for running my Angular dev-server. Unfortunately, it seems that ngx-socket-io does not function properly with the ts_devserver by default. Upon checking the browser consol ...

Struggling with sluggish performance on a certain project within VS Code

My experience with VS code has been excellent over the years, but I recently encountered a problem in one of my projects that caused a significant slowdown in performance. Strangely, other projects are working fine without any issues on VS code. I suspect ...