"Navigating through events with confidence: the power of event

Imagine I am developing an event manager for a chat application. Having had success with event maps in the past, I have decided to use them again.

This is the structure of the event map:

interface ChatEventMap {
    incomingMessage: string;
    newUser: {
        name: string;
        id: number;
    };
}

type EvType = keyof ChatEventMap;
type Ev<T extends EvType> = ChatEventMap[T];

To listen to events, we have an on function that takes two arguments: the event type and a callback function that receives the event data.

The implementation looks like this:

function on<T extends EvType>(type: T, callback: (data: Ev<T>) => void) {
    // do something
}

on('newUser', user => {
    console.log(user.name); // No errors!
});

However, there is now a requirement to listen to ALL events simultaneously. To address this, I considered creating an onEvent function that only accepts a callback containing the event type and its data.

The issue arises when trying to perform type guarding inside the callback function!

function onEvent(callback: <T extends EvType>(
    ev: { type: T; data: ChatEventMap[T] },
) => void) {
    // do something
}

onEvent(ev => {
    if (ev.type === 'newUser') {
        console.log(ev.data.name); // Error: Property 'name' does not exist on type 'ChatEventMap[T]'
    }
});

What could be the mistake here?

TS Playground

Answer №1

Check out this functional code snippet

interface ChatEventMap {
  incomingMessage: string;
  newUser: {
    id: number;
    name: string;
  };
}

type EvType = keyof ChatEventMap;
type Ev<T extends EvType> = ChatEventMap[T];

// TypeScript love in action
type Mapped = {
  [P in keyof ChatEventMap]: {
    type: P, data: ChatEventMap[P]
  }
}


function on<T extends EvType>(type: T, callback: (data: Ev<T>) => void) {
  // do stuff
}

on('newUser', user => {
  console.log(user.name); // No errors!
});

function onEvent(callback: (ev: Mapped[EvType]) => void) { }

onEvent(ev => {
  if (ev.type === 'newUser') {
    console.log(ev.data.name); // ok
  }
  if (ev.type === 'incomingMessage') {
    console.log(ev.data); // ok, string
  }
});

Sometimes, generics may not be the optimal choice.

Remember that these two functions have different implementations:

function onEvent(callback: <T extends EvType>(ev: Mapped[T]) => void) { }

function onEvent(callback: (ev: Mapped[EvType]) => void) { }

Answer №2

If you want to handle events in a simple way, you can use the following code snippet:

type EventHandler = <E extends keyof EventMap>(event: { type: E, data: EventMap[E]}) => void

function handleEvent(callback: EventHandler) {
  // Perform actions
}

Answer №3

Playing around with the problem a little bit yielded some interesting results.

Check out my approach here.

An explicit type guard was attempted to be implemented, and it seems like the first one works fine:

function isNewUserChatEvent(chatEvent: ChatEvent<any>): chatEvent is ChatEvent<'newUser'> {
    return chatEvent.type === 'newUser'
}

However, the second one did not yield the expected result, possibly due to the way type guarding operates within the function:

function isNewUserChatEvent2<EventType extends EventTypes>(chatEvent: ChatEvent<EventType>): chatEvent is ChatEvent<'newUser'> {
    return chatEvent.type === 'newUser'
}

The outcome of the above code snippet can be summarized as follows:

A discrepancy in assigning type predicates to their corresponding parameters.
  The given 'ChatEvent<"newUser">' type cannot be assigned to 'ChatEvent<EventType>'.
    Specifically, 'newUser' does not match the provided 'EventType'.
  'newUser' aligns with the constraint for 'EventType,' yet 'EventType' may encompass other subtypes such as 'incomingMessage' or 'newUser.'

Hence, since T extends EvType limits T to any implementation of EvType, it fails to ensure the precise type of data.

Feel free to explore if this information leads to discovering an improved solution!

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

Issue in TypeScript where object properties may still be considered undefined even after verifying using Object.values() for undefined values

I'm encountering an issue with TypeScript regarding my interface MentionItem. Both the id and value properties are supposed to be strings, but TypeScript is flagging them as possibly string | undefined. Interestingly, manually checking that id and va ...

What is the best way to set an array as the value for a state variable?

Looking at my function, I have the following situation: execute(e) { let { items } = this.context; let array: number[] = []; for (var i = 0; i < 2; i++) array = [...array, i]; this.setState( { items: array, } ) ...

Why isn't the customer's name a part of the CFCustomerDetails class?

Currently, I am utilizing the cashfree-pg-sdk-nodejs SDK to integrate Cashfree payment gateway into my application. Upon examining their source code, I noticed that the CFCustomerDetails class does not include the customerName attribute. https://i.stack.i ...

Using Typescript with Styled-Components and Styled-System: Unable to find a matching overload for this function call

Encountering the infamous "No overload matches this call" error when using a combination of Typescript, Styled-Components, and Styled-System. I've come across solutions that suggest passing a generic type/interface to the styled component, like the o ...

Exploring TypeScript Generics and the Concept of Function Overloading

How can I create a factory function that returns another function and accepts either one or two generic types (R and an optional P) in TypeScript? If only one generic type is provided, the factory function should return a function with the shape () => ...

Encountering an issue with testing CKEditor in Jest

Testing my project configured with vite (Typescript) and using jest showed an error related to ckeditor. The error is displayed as follows: [![enter image description here][1]][1] The contents of package.json: { "name": "test-project" ...

Utilizing Typescript for directive implementation with isolated scope function bindings

I am currently developing a web application using AngularJS and TypeScript for the first time. The challenge I am facing involves a directive that is supposed to trigger a function passed through its isolate scope. In my application, I have a controller r ...

`Angular 9 template directives`

I am facing an issue with my Angular template that includes a ng-template. I have attempted to insert an embedded view using ngTemplateOutlet, but I keep encountering the following error: core.js:4061 ERROR Error: ExpressionChangedAfterItHasBeenCheckedEr ...

Issues arise when upgrading from Angular 8 to 9, which can be attributed to IVY

After successfully upgrading my Angular 8 application to Angular 9, I encountered an error upon running the application. { "extends": "./tsconfig.json", "compilerOptions": { "outDir": ". ...

Why is my data not showing correctly? - Utilizing Ionic 3 and Firebase

I'm experiencing a challenge with displaying Firebase data in my Ionic 3 application. Below is the relevant snippet of code from my component where 'abcdef' represents a placeholder for a specific user key: var ref = firebase.database().ref ...

To properly display a URL on a webpage using Angular, it is necessary to decode

After my console.log in Angular 5 service call to the component, I can see the correct data URL being displayed http://localhost:4200/inquiry?UserID=645 However, when it is inside an Angular for loop in the HTML template, it displays http://localhost:42 ...

Emphasize a word in a Typescript text by wrapping it in an HTML tag

I've been experimenting with using HTML tags within TypeScript to highlight specific words in a sentence before displaying the sentence in HTML. Despite searching on StackOverflow for various solutions, I haven't been able to find one that works. ...

The validator is incorrectly diagnosing the input as 'invalid' when in reality it is not

Currently, I am working on creating a text field that must not be empty and should also not start with a specific set of characters (let's say 'test'). For example, I want testxyz or an empty field to be considered invalid, while anything e ...

How to arrange table data in Angular based on th values?

I need to organize data in a table using <th> tags for alignment purposes. Currently, I am utilizing the ng-zorro table, but standard HTML tags can also be used. The data obtained from the server (via C# web API) is structured like this: [ { ...

Generate sample data within a fixture

Currently, I am in the process of working on a project that involves creating users and conducting tests on those users. To generate user data such as first name and last name, I am utilizing the faker tool. My goal is to create a user with these generated ...

What is the best approach for setting up a global pipe that can be utilized across various modules?

One of my Angular projects includes a custom pipe called CurrConvertPipe. import {Pipe, PipeTransform} from '@angular/core'; import {LocalStorageService} from './local-storage'; @Pipe({name: 'currConvert', pure: false}) expor ...

Handling linting errors for getInitialProps return type in NextJS with Typescript

I've been grappling with this problem for an extended period, and despite numerous attempts, I haven't been able to resolve it. My issue revolves around a basic page named one.tsx. Its structure is as follows: https://i.sstatic.net/xP89u.png T ...

Display fresh information that has been fetched via an HTTP request in Angular

Recently, I encountered an issue where data from a nested array in a data response was not displaying properly in my component's view. Despite successfully pushing the data into the object programmatically and confirming that the for loop added the it ...

Retrieving the key from an object using an indexed signature in Typescript

I have a TypeScript module where I am importing a specific type and function: type Attributes = { [key: string]: number; }; function Fn<KeysOfAttributes extends string>(opts: { attributes: Attributes }): any { // ... } Unfortunately, I am unab ...

How to send multiple queries in one request with graphql-request while using getStaticProps?

I am currently utilizing graphCMS in combination with NextJS and have successfully implemented fetching data. However, I am facing an issue where I need to execute 2 queries on the homepage of my website - one for all posts and another for recent posts. q ...