Deduce the argument type of a class from the super call

I'm currently working on a project utilizing the discord.js library. Within this project, there is an interface called ClientEvents which contains different event argument tuple types:

interface ClientEvents {
  ready: [];
  warn: [reason: string]
  message: [message: Message];
  // ...some more events
}

Alongside this, I have created an EventHandler class that accepts a specific type as an argument:

abstract class EventHandler<E extends keyof ClientEvents> {

  protected constructor(public eventName: E) {}

  abstract execute(...args: ClientEvents[E]): void;
}

However, I've encountered an issue where I am unable to extend this class using type inference for E, even when explicitly defined in the super() call:

// Error: Generic type 'EventHandler<E>' requires 1 type argument(s).
class ReadyHandler extends EventHandler {

  public constructor() {
    super('ready'); // My intention was to infer the type using this argument
  }

  public execute() {
    // Additional code goes here
  }
}

Is there a method to automatically infer class argument types based on the arguments provided in a super() call?

Answer №1

Unfortunately, inference doesn't usually work that way, so it's not easy to achieve. One option is to explicitly write out the code:

class ReadyHandler extends EventHandler<"ready"> {    
  public constructor() {  super('ready');  }    
  public execute() {}
}

If you want to avoid redundancy, you can refactor or encapsulate the EventHandler by passing an actual string of type E for inference. This approach would result in a non-generic class. Here's an example:

function SpecificHandler<E extends keyof ClientEvents>(eventName: E) {
  abstract class SpecificHandler extends EventHandler<E> {
    constructor() { super(eventName) };
  }
  return SpecificHandler;
}

class MessageHandler extends SpecificHandler("message") {
  public execute(msg: Message) {

  }
}

However, the trade-off may not be worth it.

Explore the code on TypeScript Playground

Answer №2

When creating an instance of a class, the generic type can be inferred from a constructor argument. However, when subclassing, this inference does not occur with the use of `super`. In TypeScript, the generic type needs to be included in the type definition when extending a class. This could be because TypeScript may not know if the generic type is required before `super` is called.

To work around this limitation, you can also make the subclass generic and pass the generic type to the superclass.

type EventKeys = keyof ClientEvents;

class ReadyHandler<E extends EventKeys> extends EventHandler<E> {
  constructor() {
    // A cast is necessary to prevent TS from raising an error.
    super('ready' as E);
  }
}

In this scenario, the generic type `E` is correctly inferred from the constructor of `ReadyHandler`, allowing you to call `new ReadyHandler()` without specifying the type argument.

Another approach mentioned in the other response is to explicitly specify the type as in `EventHandler<'ready'>`, which requires writing `'ready'` twice. The choice between these methods depends on your preference for convenience.

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

The resolver function in the Nextjs higher order API is not defined

I am trying to create a custom wrapper function for my NextJs API routes that will verify a JWT in the request, validate it, and then execute the original API handler. Here is how I have defined my wrapper function: interface ApiError { message: string, ...

The interface is incompatible with the constant material ui BoxProps['sx'] type

How can I make the interface work for type const material ui? I tried to register an interface for sx here, but it keeps giving me an error. import { BoxProps } from '@mui/material'; interface CustomProps { sx: BoxProps['sx&apo ...

What are the best methods for visually designing a database using Entity Framework Core?

I find myself contemplating the best approach to designing my database scheme for optimal efficiency and visual appeal. Currently, I am working on an ASP.NET Core application with Angular 2, utilizing Entity Framework Core ("Microsoft.EntityFrameworkCore" ...

The numerical value of zero in React Native TypeScript is being interpreted as NaN

When attempting to map an array in React Native (Android) and use its values or keys as data for return, I encountered an issue where the value 0 is read as NaN. This problem also arose when utilizing a typescript enum. The versions I am using are: typesc ...

Creating a fresh ngx-translate pipeline (comparing pure and impure methods)

Edit: I am looking to enhance the functionality of ngx-translate's pipe by extending it. Here is an example of how I achieved this: import { Pipe, PipeTransform } from '@angular/core'; import { TranslatePipe } from "@ngx-translate/core"; @ ...

retrieve the router information from a location other than the router-outlet

I have set up my layout as shown below. I would like to have my components (each being a separate route) displayed inside mat-card-content. The issue arises when I try to dynamically change mat-card-title, as it is not within the router-outlet and does not ...

"Encountering a 500 error on Chrome and Internet Explorer while trying to sign

I am currently working on an ASP.NET Core application that handles identity management through Azure AD B2C using the ASP.Net Core OpenID Connect. The front end is developed using AngularJS 2 with TypeScript. In my Logout function, the user is redirected t ...

What is the best way to add a service to a view component?

I am facing an issue with my layout component where I am trying to inject a service, but it is coming up as undefined in my code snippet below: import {BaseLayout, LogEvent, Layout} from "ts-log-debug"; import {formatLogData} from "@tsed/common/node_modul ...

How to integrate a chips feature in Angular 4 using Typescript

Struggling to incorporate a chips component into my Angular web application, which comprises Typescript, HTML, and CSS files. After grappling with this for weeks without success, I have yet to find the right solution. To review my current code, you can a ...

Issue with Angular component inheritance where changes made in the base component are not being

click here to view the example on your browser base component import { Component, ChangeDetectorRef, OnInit, Input } from '@angular/core'; @Component({ selector: 'app-base-component', template: `<p> <b>base</b> ...

Removing the final element within a nested array: a step-by-step guide

let originalArray=[ [ "Test1", "4", "160496" ], [ "Test2", "6", "38355" ], [ "Test3", "1", "1221781" ], [ " ...

React - All subsequent variable declarations must be of the same type

Running webpack in my React application results in the following error message appearing 58 times across different variables: https://i.sstatic.net/uedR7.png Removing the @types directory did not resolve the issue and instead produced a new error message: ...

Encountering a Lint No Nested Ternary Error while utilizing the ternary operator

Is there a way to prevent the occurrence of the "no nested ternary" error in TypeScript? disablePortal options={ // eslint-disable-next-line no-nested-ternary units=== "mm&quo ...

Encountered 'DatePickerProps<unknown>' error while attempting to develop a custom component using Material-UI and react-hook-form

Currently, I'm attempting to create a reusable component using MUI Datepicker and React Hook Form However, the parent component is throwing an error Type '{ control: Control<FieldValues, object>; name: string; }' is missing the follow ...

What is the significance of the 'this' context type void not being assignable to type rule?

Currently, I am conducting tests using Typescript to explore the feasibility of a project I have in mind. In one of my functions, I need to specify the type of 'this' as a class. While it technically works, I continue to encounter an error messag ...

Guide on setting up a route in Next.js

Recently, I developed a simple feature that enables users to switch between languages on a webpage by adding the language code directly after the URL - i18n-next. Here's a snippet of how it functions: const [languages, ] = React.useState([{ langua ...

Mastering mapped types to replace properties in Typescript

I have created a Factory function where it takes an object as input and if that object contains specific properties, the factory transforms those properties into methods. How can I utilize mapped Types to accurately represent the type of the resulting obj ...

Unable to run `create-react-app` with the `--template typescript` option

Every time I try to execute the following command: npx create-react-app my-app --template typescript only a JavaScript project is created, without any .tsx files. After consulting the CRA's TypeScript guide, it appears that the command requires Node ...

The object's key values were unexpectedly empty despite containing data

There's an issue with my object - it initially gets key values, but then suddenly loses them all. All the key values become empty and a message appears in the console for the object: "this value was evaluated upon first expanding. it may have ch ...

Annoying glitch when using http get in Ionic (version 3.19.0)

Issue: Having trouble with Ionic's http get function, as I keep running into this error message: Typescript Error Expected 1-2 arguments, but got 3. The line causing the problem seems to be similar to this: this.http.get('http://127.0.0.1 ...