Typing function parameters that accept generic types "from the identical instance"

I have come across a similar implementation of an Event Emitter in TypeScript and I am looking to create a helper function that maintains type integrity. You can find my code attempt on TS Play sample page. Below is the snippet from my sample:

In the provided sample, I am only given a generic eventName: string, and callback: (e: any) => void as output. Is there a way to link these three arguments to the same instance of these generics?

import EventEmitter from 'events';

type EventMap = Record<string, any>;

type EventKey<T extends EventMap> = string & keyof T;
type EventReceiver<T> = (params: T) => void;

interface Emitter<T extends EventMap> {
  on<K extends EventKey<T>>
    (eventName: K, fn: EventReceiver<T[K]>): void;
  off<K extends EventKey<T>>
    (eventName: K, fn: EventReceiver<T[K]>): void;
  emit<K extends EventKey<T>>
    (eventName: K, params: T[K]): void;
}

export class MyEmitter<T extends EventMap> implements Emitter<T> {
  private emitter = new EventEmitter();
  on<K extends EventKey<T>>(eventName: K, fn: EventReceiver<T[K]>) {
    this.emitter.on(eventName, fn);
  }

  off<K extends EventKey<T>>(eventName: K, fn: EventReceiver<T[K]>) {
    this.emitter.off(eventName, fn);
  }

  emit<K extends EventKey<T>>(eventName: K, params: T[K]) {
    this.emitter.emit(eventName, params);
  }
}

function onEmit<
  T extends EventMap,
  K extends EventKey<T>
>(
  emitter: MyEmitter<T>,
  eventName: K,
  callback: EventReceiver<T[K]>,
) {
  emitter.on(eventName, callback);
}

class Foo extends MyEmitter<{ foo: number }> {}
const foo = new Foo();
foo.on('foo',
  (value) => console.log('foo was', value.toFixed(2))
  // ^?
)

onEmit(foo, 'foo',
  (value) => console.log('foo was', value.toFixed(2))
  // ^?
)

Answer №1

The issue at hand arises from the inconsistency of TypeScript when inferring type T from a type that can be assigned to MyEmitter<T>. This is due to the indirect dependence of MyEmitter<T> on T, making it difficult for the compiler to deduce the correct type. The relevant type declarations are as follows:

declare class MyEmitter<T extends EventMap> {   
  private emitter: EventEmitter;
  on<K extends EventKey<T>>(eventName: K, fn: EventReceiver<T[K]>): void;
  off<K extends EventKey<T>>(eventName: K, fn: EventReceiver<T[K]>): void;    
  emit<K extends EventKey<T>>(eventName: K, params: T[K]): void;
}

When the compiler encounters a type Foo and needs to determine MyEmitter<infer T> from it, it tries to perform a structural comparison between Foo and MyEmitter to find possible candidates for T. However, in some cases, such as with the emit method example:

<K extends "foo">(eventName: K, params: { foo: number; }[K]) => void

the inference fails because the compiler cannot infer {foo: number}. This leads to the fallback constraint of EventMap, which may not produce the desired outcome.


To address this challenge without advanced inference mechanisms, one workaround is to introduce a more direct structural dependency of MyEmitter<T> on T. One approach is to add a member of type T, which establishes a clear connection. Alternatively, incorporating a function type with parameters or return type of T can achieve the same effect. An identity function serves this purpose well, like so:

class MyEmitter<T extends EventMap> {
  private __hint = (t: T) => t;   
  // remaining implementation remains unchanged
}

This ensures that MyEmitter<infer T> can succeed via a structural check, offering a potential solution to the inference issue.

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

Troubleshooting the "Request failed with status code 500" error when refreshing a page in a React application

Every time the page is reloaded, an error message pops up saying: Uncaught (in promise) Error: Request failed with status code 500. Here's the code in list.tsx: const [state, setState] = useState([]); const { getRoom } = useRoom(); const fe ...

Avoid the occurrence of the parent's event on the child node

Attempting to make changes to an existing table created in react, the table is comprised of rows and cells structured as follows: <Table> <Row onClick={rowClickHandler}> <Cell onCLick={cellClickHandler} /> <Cell /> ...

`Is it possible to integrate npm libraries with typescript and ES6?`

I am looking to focus on using ES6 as the output for a node server-side app that I plan to run on the cutting-edge iojs distribution, which hopefully has support for the latest ES6 syntax. However, I'm unsure about how to integrate standard NPM libra ...

I am interested in showcasing a distinct screen layout specifically designed for mobile device viewing

Looking to display different screens for PC and smartphone users. I am using react, Typescript, and next.js for development. Specifically, I need to show user.tsx when accessing the /user URL from a PC, and Accessdenied.tsx when accessing it from a smartp ...

Access-Control-Allow-Origin header not being sent by ExpressJS

In the midst of my project, I find myself needing an angular web application to connect with a node/express backend. Despite trying to implement Cors for this purpose, the express server refuses to send the Access-Control-Allow-Origin header. I am perplexe ...

Can the return type of a function be utilized as one of its argument types?

When attempting the following code: function chain<A, B extends (input: A, loop: (input: A) => C) => any, C extends ReturnType<B>> (input: A, handler: B): C { const loop = (input: A): C => handler(input, loop); return loop(input) ...

Cannot display value in NumericFormat when using MUI TextField due to prefix restrictions

When using MUI TextField with the NumericFormat and prefix prop, there seems to be an issue. If I start typing a number quickly, only one digit gets registered. On the other hand, if I type slowly all my numbers show up but the prefix disappears. All inp ...

The data in DataTables is loading correctly, however, the buttons and sorting features are not operating as intended within Angular 4

I am currently working on an Angular 4 project where I need to display data in a table format using the DataTables library. While I have successfully implemented the hardcoded examples provided by DataTables with all the buttons functioning as expected, I ...

Getting environment variable from JSON file within Angular 4 - a step-by-step guide

I have a file named "appsettings.json" which contains configurations for a specific purpose. I want to include variables from both "environment.ts" and "environment.prod.ts" in this file and access them within the environment files. When I attempt to impo ...

Input tag paired with a date picker

Instead of using the <label>{{list.createdat | date: 'dd.MM.yyyy'}}</label> tag, I want to change it to an input tag to create a simple datepicker: <input type="date"/> The issue is that the data in the <label> tag is pa ...

Change (EU Time) date format of dd/mm/yyyy hh:mm:ss to a timestamp

Is there a way to convert time into a timestamp? I attempted to use .getTime(), but it seems to be switching the day and month. const date = new Date('01-02-2003 01:02:03'); console.log(date.getTime()); It appears to be converting to the US Tim ...

The use of `super` in Typescript is not returning the expected value

Trying to retrieve the name from the extended class is causing an error: Property 'name' does not exist on type 'Employee'. class Person { #name:string; getName(){ return this.#name; } constructor(name:string){ ...

How can you deduce the type from a different property in Typescript?

I have encountered obstacles in my development process and need assistance overcoming them. Currently, I am trying to configure TObject.props to only accept 'href' or 'download' if the condition TObject.name = 'a' is met, and ...

Utilizing TypeScript's noUncheckedIndexedAccess for secure array access

I'm currently investigating the reasons behind TypeScript allowing array access in the first scenario mentioned below, but not in the second. Despite having noUncheckedIndexedAccess enabled, I am ensuring that the accessed objects are not undefined be ...

Troubleshooting React TypeScript: Resolving the Error "Argument of type ''""' is not assignable to parameter of type 'SetStateAction<undefined>'"

Currently, I am troubleshooting a React application that was extracted from a live server and now I am attempting to run it on my local machine. However, upon starting up the application locally, I encountered the following error message: Argument of ty ...

Encountering a NPM error when trying to launch the server using ng serve

I encountered an error : ERROR in /opt/NodeJS/FutureDMS/src/app/app.module.ts (5,9): Module '"/opt/NodeJS/FutureDMS/src/app/app.routing"' has no exported member 'APP_ROUTE'. Within my code, I have utilized arrow function in the loadCh ...

Retrieve highlighted text along with its corresponding tag in ReactJS

my <span class="highlight">highlighted</span> word The text above is showing an example including HTML tags. However, when using window.getSelection(), only the text "my highlighted word" is returned without the surrounding <span& ...

A common method for incorporating personalized react-scripts into create-react-app

After creating a project using create-react-app in TypeScript, I am looking to integrate custom react-scripts without ejecting. What is the most effective approach to achieve this? ...

Decorator in React that automatically sets the display name to match the class name

Is there a way to create a decorator that will automatically set the static property displayName of the decorated class to the name of the class? Example usage would be: @NamedComponent class Component extends React.Component { \* ... *\ } ...

Iterate through the complex array of nested objects and modify the values according to specified conditions

I am currently iterating through an array of objects and then delving into a deeply nested array of objects to search for a specific ID. Once the ID is found, I need to update the status to a particular value and return the entire updated array. Issue: Th ...