Determining the function argument type by leveraging a discriminated union in TypeScript based on another argument

In my code, I have a discriminated union that describes different event types. The addEventHandler function is used to add an event handler by providing the event ID as the first argument and the event callback as the second argument. The event ID field serves as the discriminator for the union type.

interface TestEventA {
  id: 'TestEventA';
  payload: null;
}

interface TestEventB {
  id: 'TestEventB';
  payload: TestEventBPayload;
}
interface TestEventBPayload {
  content: string;
}

interface TestEventC {
  id: 'TestEventC';
  payload: TestEventCPayload;
}
interface TestEventCPayload {
  value: number;
}

type TestEvent = (TestEventA | TestEventB | TestEventC);

function addEventHandler<T extends TestEvent['id']>(eventId: T, listener: (data: (TestEvent & { id: T })) => void) {
  eventEmitter.on(eventId, listener);
}


addEventHandler('TestEventC', (event) => {
  // My issue is that event is not inferred to be of type TestEventC, instead it is a combination of various types

  // event.payload: Object is possibly 'null'
  // Property 'value' does not exist on type 'TestEventBPayload | TestEventCPayload'
  console.log(event.payload.value);

  if (event.id === 'TestEventC') { // redundant condition
    console.log(event.payload.value); // No error here
  }
});

I am struggling with how to enforce the callback function within addEventHandler to infer the correct type of the event. Any suggestions?

Answer №1

If you're looking for a way around it, one solution is to develop an interface that connects event identifiers to data payloads. Here's an example:

interface TestEventBPayload {
  content: string;
}

interface TestEventCPayload {
  value: number;
}

interface Events {
  TestEventA: null,
  TestEventB: TestEventBPayload,
  TestEventC: TestEventCPayload
};

function addEventHandler<TId extends keyof Events>(
  eventId: TId,
  listener: (data: Events[TId] & { id: TId }) => void
): void {
  eventEmitter.on(eventId, listener);
}

addEventHandler('TestEventC', (event) => {
  console.log(event.value);

  if (event.id === 'TestEventC') {
    console.log(event.value);
  }
});

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

Struggling with establishing connection logic between two database tables using Prisma and JavaScript

I'm facing a perplexing logic problem that is eluding my understanding. Within the context of using next-connect, I have a function designed to update an entry in the database: .put(async (req, res) => { const data = req.body; const { dob ...

The Angular framework is unable to locate a differ that supports the object '[object Object]' which is of type 'object'

In my Angular project, I am calling an API and receiving the following JSON data: { "id": 16, "poste": "ameur taboui", "desciption": "f", "service": "f", ...

Is it possible for properties.load() to take in something other than an inputStream?

The documentation for java.util.Properties.load() states that it only accepts a java.io.InputStream. So why am I able to successfully load the properties using a FileReader, which is not a subclass of InputStream? Below is an example that prints the prope ...

Upgrading Angular from version 8 to 9: Dealing with Compilation Errors in Ivy Templates

After successfully upgrading my Angular application from version 8 to version 9 following the Angular Update Guide, I encountered some issues in the ts files and managed to resolve them all. In version 8, I was using the View Engine instead of Ivy, which ...

The program encountered an issue where it was unable to access the property 'ObjectTracker' because it was

I am relatively new to Angular development and I am attempting to incorporate into my Typescript Angular application. Even though I have included the type definitions, I am encountering an issue: MonitorComponent_Host.ngfactory.js? [sm]:1 ERROR TypeErr ...

Invoke a TypeScript function from the HTML code embedded within a TypeScript component

In my pop-up window, there are 2 buttons: Update and Delete. I need to implement functionality so that when the Update button is clicked, the current pop-up should disappear and a new editable pop-up with the same fields should appear, along with two addit ...

Determine the class of an object within the "keyof" parameter by utilizing both property and generic types

I have a requirement to create an interface with generic types that can accept an object with keys representing "root field names" and values as arrays of objects defining sub-fields with the key as the name of the sub-field and the type as the value' ...

An error has occurred: TypeError - The class constructor $b802fbb11c9bd2dc$export$2e2bcd8739ae039 must be called with 'new'

I am attempting to integrate emoji-mart into my application, but I keep encountering a persistent error. Here is the snippet of code causing the issue: import data from '@emoji-mart/data' import { Picker } from 'emoji-mart' {showEmoji ...

Angular HTTP: GET 404 response from server

I have created this HTTP request method: public isExisting(id: string): Observable<boolean> { const createURL = () => map((userId: string) => this.createIdURL(userId)); const createResponse = () => map(() => true); const hand ...

Angular Material: Enable Window Dragging Across Multiple Monitors

Exploring the usage of Angular Material Dialog or any other Popup Window Component. The functionality is mostly working as expected, with the exception of the last point. a) The original screen should not have a grey overlay, b) Users should be able to i ...

Entering key-value pairs into a dictionary to show correlation

I've been struggling to find a solution for what seems like a simple issue. The problem lies in typing a dictionary with values of different types so that TypeScript can infer the type based on the key. Here is the scenario: type Id = string; inter ...

An error occurred within Angular 8 while attempting an HTTP post request, as it was unable to access the 'message' property of

After conducting a search on Stack Overflow, I found a similar question that relates to my issue. Login OTP Component: onSubmitValidOTP() { this.authenticationService.ValidOTP(this.fo.OtpControl.value, username, role) .pipe(first ...

This phrase cannot be invoked

My code seems correct for functionality, but I am encountering an error in my component that I do not know how to resolve. Can someone please help me with this issue? This expression is not callable. Not all constituents of type 'string | ((sectionNa ...

VueJS - When using common functions, the reference to "this" may be undefined

I'm struggling to extract a function that can be used across various components. The issue is that when I try to use "this", it is coming up as undefined. I'm not sure of the best practice for handling this situation and how to properly assign th ...

Troubleshooting a Typescript typing problem within the map function for mixed types in a React

I have created two object types, Team and Position, which are both part of an array that I loop through in my react component. When I try to iterate over the array using the map function, I encounter the following errors: Here are some examples of the er ...

Setting a restriction on the maximum number of keys allowed to be passed through generics and indexed access types in Typescript for a specific function

Apologies for the title confusion, let me clarify my problem. I am utilizing a React state management library where my application state is structured as follows: type ApplicationState = { loading: boolean; data: string[]; colors: number[]; alerts ...

Navigate back to the initial page in Ionic2 using the navpop function

I have an application that needs to guide the user step by step. While I am aware of using navpop and navpush for navigating between pages, I am unsure about how to use navpop to go back to the first page. Currently, I am attempting to pop() twice if ther ...

Checking JavaScript files with TSLint

After spending many hours attempting to make this work, I still haven't had any success... I am wondering: How can I utilize TSLint for a .js file? The reason behind this is my effort to create the best possible IDE for developing numerous JavaScrip ...

Stop repeated form submissions in Angular using exhaust map

How can we best utilize exhaust Matp to prevent multiple submissions, particularly when a user is spamming the SAVE button? In the example provided in the code snippet below, how do we ensure that only one submission occurs at a time even if the user click ...

Script - Retrieve the content of the code element

I am currently developing an extension for VS Code that will enhance Skript syntax support. One challenge I am facing is the inability to select the body of the code block. Skript syntax includes various blocks such as commands, functions, and events, eac ...