Tips on narrowing down the type of callback event depending on the specific event name

I've been working on implementing an event emitter, and the code is pretty straightforward.

Currently, tsc is flagging the event type in eventHandler as 'ErrorEvent' | 'MessageEvent'. This seems to be causing some confusion, and I'm looking for a way to get tsc to narrow down the types correctly based on the eventName passed to the on function.

Is there a solution to this issue?

Here are the Definitions:

interface Event {
  code: string;
}

interface ErrorEvent extends Event {
  xxx: number;
}

interface MessageEvent extends Event {
  yyy: number;
}

interface Events {
  error: ErrorEvent,
  message: MessageEvent
}

type EventHandler = (event: Events[keyof Events]) => void;

Here's the TS CODE:

function on (eventName: keyof Events, eventHandler: EventHandler) {
  console.log(eventName)
}

on('message', (event) => { // ErrorEvent | MessageEvent
  console.log(event)
  console.log(event.code)
  console.log(event.xxx) // Property 'xxx' does not exist on type 'ErrorEvent | MessageEvent'.
  console.log(event.yyy) // Property 'yyy' does not exist on type 'ErrorEvent | MessageEvent'.
})

Answer №1

To start, eliminate the EventHandler as a union type:

type EventHandler<K extends EventKeys> = (event: Events[K]) => void

By doing this, you will have a specific type for each concrete K. Next step is to ensure that the second argument type of the on function relies on the first. Therefore, you need to add a type parameter to your function in order to align the types:

function on <K extends EventKeys>(eventName: K, eventHandler: EventHandler<K>): void {}

This should work as intended:

interface Event {
  code: string;
}

interface ErrorEvent extends Event {
  xxx: number;
}

interface MessageEvent extends Event {
  yyy: number;
}

interface Events {
  error: ErrorEvent,
  message: MessageEvent
}

type EventKeys = keyof Events
type EventHandler<K extends EventKeys> = (event: Events[K]) => void;

function on <K extends EventKeys>(eventName: K, eventHandler: EventHandler<K>): void {
  console.log(eventName)
}

on('message', (event) => {
  console.log(event)
  console.log(event.code)
  console.log(event.xxx) //  Property 'xxx' does not exist on type 'MessageEvent<any>'.
  console.log(event.yyy)
})

TS playground

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

Enhancing Vue functionality with vue-class-component and Mixins

In my Vue project, I am using vue-class-component along with TypeScript. Within the project, I have a component and a Mixin set up as follows: // MyComp.vue import Component, { mixins } from 'vue-class-component' import MyMixin from './mixi ...

Add a hyperlink within a button element

I am looking to add a route to my reusable 'button' component in order to navigate to another page. I attempted using the <Link> </Link> tags, but it affected my CSS and caused the 'button' to appear small. The link works if ...

Using Angular, create mat-checkbox components that are dynamically generated and bound using

In my Offer class, I have a property called "units" which is an array of objects. export class Offer { public propertyA: string; public propertyB: string; public units: Unit[]; } export class Unit { public code: string, public name: ...

Loading and unloading an Angular 6 component

One challenge I'm facing involves creating an image modal that appears when an image is clicked. Currently, I have it set up so that the child component loads upon clicking the image. However, the issue is that it can only be clicked once and then dis ...

Shifting attention to an angular 6 form field

I am developing an application in Angular which involves creating, reading, updating, and deleting user information. I have a requirement for the username to be unique and need to display an error message if it is not unique, while also focusing on the use ...

Guide to highlighting manually selected months in the monthpicker by utilizing the DoCheck function in Angular

I'm facing an issue and I could really use some assistance. The problem seems quite straightforward, but I've hit a roadblock. I have even created a stackblitz to showcase the problem, but let me explain it first. So, I've developed my own t ...

Encountering issues when verifying the ID of Angular route parameters due to potential null or undefined strings

Imagine going to a component at the URL localhost:4200/myComponent/id. The ID, no matter what it is, will show up as a string in the component view. The following code snippet retrieves the ID parameter from the previous component ([routerLink]="['/m ...

The subscription function in observables may result in values that are undefined

I integrated a new angular 2 library into my application called "angular2-grid". This library is located within the node_modules folder. Furthermore, I created a service as shown below: import { Injectable } from '@angular/core'; import { Htt ...

Guide to Re-rendering a component inside the +layout.svelte

Can you provide guidance on how to update a component in +layout.svelte whenever the userType changes? I would like to toggle between a login and logout state in my navbar, where the state is dependent on currentUserType. I have a store for currentUserTyp ...

The observable HTTP map appears to be more of a representation rather than a concrete entity

I seem to be struggling with understanding Typescript I expected the returned observable to have a method getTitle(), but it seems to be missing. Instead, I am getting an object that resembles AttachableContentModel without that particular method. What is ...

What is the reason for the lack of compatibility between the TypeScript compilerOptions settings 'noEmitOnError: true' and 'isolatedModules: false'?

Whenever I try to execute TypeScript with isolatedModules set as true and then false, I keep encountering this error message: tsconfig.json(5,9): error TS5053: Option 'noEmitOnError' cannot be specified with option 'isolatedModules'. ...

"React with Typescript - a powerful combination for

I'm facing an issue trying to create a simple list of items in my code. Adding the items manually works, but when I try to map through them it doesn't work. Apologies for any language mistakes. import './App.css' const App = () => { ...

Tips on preventing image previews from consuming too much text data while updating a database in Angular 12 using Material UI for image uploads within a FormGroup object

Currently working with Angular 12 and Angular Material for image uploads with preview. I have a formgroup object below, but I'm running into issues with the 197kb image preview text being inserted into the database. Despite trying setValue/patchValue/ ...

js TouchEvent: When performing a pinch gesture with two fingers and lifting one of them up, how can you determine which finger was lifted?

I am currently working on a challenging touching gesture and have encountered the following issue: let cachedStartTouches: TouchList; let cachedMoveTouches: TouchList; function onStart(ev: TouchEvent) { // length equals 2 when two fingers pinch start ...

Creating a Higher Order Component (HOC) for your Next.js page

Upon running the following code, I encountered an error message Error: The default export is not a React Component in page: "/" pages/index.tsx import React, { useState, useRef } from "react"; import type { NextPage } from "next&q ...

Error: The type '{ children: Element[]; className: string; }' cannot be assigned to the type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'

My current setup involves using Next.js and TypeScript While working in Visual Studio Code, I encountered an error message stating Type error: Type '{ children: Element[]; className: string; }' is not assignable to type 'DetailedHTMLProps&l ...

Setting values to variables within a component to enable universal access throughout the component in Angular 2

In my Angular 2 project, I have a function that retrieves data from a database using an API. I've created a function that stores the data successfully in a variable named "ReqData", which is of type "any". this._visitService.getchartData().subscrib ...

What is the correct way to use forwardRef in a dynamic import in Next.js?

I've been trying to incorporate the forwardRef in my code, but I'm facing some difficulties. Can anyone help me out with this? I'm encountering the following errors: Property 'forwardedRef' does not exist on type '{}'. ...

I am having trouble with a property that I believe should be recognized but is not

Here is the vocabulary I am working with: type MyHeaders = { Authorization: string; Accept: "application/json"; }; type MyGetOptions = { url: string; json: true; }; type MyOptionsWithHeaders = { headers: MyHeaders; }; type MyPostOptions ...

Leveraging moment.format Function in Angular within an HTML Context

Is there a way to implement the moment.format method in HTML? Currently, I am utilizing the toLocaleDateString method to showcase an array of dates: <ng-template let-event> <div>{{event.date.toLocaleDateString(' ...