Tips for ensuring the correct typing of a "handler" search for a "dispatcher" style function

Imagine having a structure like this:

type TInfoGeneric<TType extends string, TValue> = {
  valueType: TType,
  value: TValue, // Correspond to valueType
}

To prevent redundancy, a type map is created to list the potential valueType and associate it with the corresponding value's type.

type TInfoTypeMap = {
    num: number;
    str: string;
}

To construct TInfo, mapped types are utilized to map all types into TInfoGeneric and extract only the value side.

type TAllPossibleTInfoMap = {
    [P in keyof TInfoTypeMap]: TInfoGeneric<P, TInfoTypeMap[P]>;
};

type TInfo = TAllPossibleTInfoMap[keyof TAllPossibleTInfoMap]; // TInfoGeneric<"num", number> | TInfoGeneric<"str", string>

Subsequently, a separate mapped type is created solely for handlers to define handlers for all types.

type TInfoHandler = {
    [P in keyof TInfoTypeMap]: (value: TInfoTypeMap[P]) => any
};

const handlers: TInfoHandler = {
    num: (value) => console.log(value.toString(16)),
    str: (value) => console.log(value),
}

Finally, an approach to utilize the handler is devised through a function like the following:

function handleInfo(info: TInfo) {
    handlers[info.valueType](info.value); // Error
}

An error is encountered:

Argument of type 'string | number' is not assignable to parameter of type 'number & string'.
  Type 'string' is not assignable to type 'number & string'.
    Type 'string' is not assignable to type 'number'.

The situation is clear when info.valueType is either 'num' or 'str'. However, is there a way to refine the code to pass TypeScript type-checking?

Answer №1

Unfortunately, there isn't a straightforward and completely safe solution for your situation. I've raised an issue on microsoft/TypeScript#30581, but it might not get fixed anytime soon.

There are two possible approaches you can take. One option is to use a type assertion, where you override the compiler's knowledge in this specific case:

function handleInfo(info: TInfo) {
    // Use type assertion here. Not entirely safe, but convenient.
    (handlers[info.valueType] as (x: number | string)=>any)(info.value); 
}

This will remove the error, although it sacrifices some type safety. However, it remains convenient and won't impact the resulting JavaScript code.


Alternatively, you could guide the compiler through each scenario and demonstrate that everything is in order. This method is intricate, fragile, and affects runtime:

const typeGuards: {
  [P in keyof TInfoTypeMap]: (x: TInfoTypeMap[keyof TInfoTypeMap])=>x is TInfoTypeMap[P];
} = {
    num: (x:any): x is number => typeof x === "number",
    str: (x:any): x is string => typeof x === "string"
}

function narrowTInfo<K extends keyof TAllPossibleTInfoMap>(
  x: TInfo, v: K): x is TAllPossibleTInfoMap[K] {
    return typeGuards[v](x.value);
} 

function handleInfo(info: TInfo) {
    if (narrowTInfo(info, "num")) {
        handlers[info.valueType](info.value); // okay
    } else {
        handlers[info.valueType](info.value); // okay
    }
}

Although this approach works, it can be cumbersome. My suggestion would still lean towards using an assertion.

I hope this information proves helpful. Best of luck!

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

Creating an array of objects in Angular 2

I'm facing an issue with the following expression: public mySentences:Array<string> = [ {id: 1, text: 'Sentence 1'}, {id: 2, text: 'Sentence 2'}, {id: 3, text: 'Sentence 3'}, {id: 4, text: 'Sen ...

What is the best way to add a 'Drawer' component into the 'AppBar' using React MUI within the Next.js framework?

My goal is to create an App bar with a 'hamburger' icon on the left that, when clicked, will display a Sidenav (drawer). I am working with React Material and Next.js (App router) and need to have the app bar and drawer as separate components, hea ...

Tips for efficiently handling state across various forms in separate components using only one save button within a React-Redux application

I am currently developing an application that combines a .NET Core backend with a React frontend, using React Hook Form for managing forms. Unlike typical single-page applications, my frontend is not built in such a way. On a specific page of the applicat ...

ngPrime table column selection and data extraction

I am looking to extract multiple columns from a table. Can anyone suggest the best approach for this? Does NGPrime offer any functionality for achieving this task? Appreciate your help! ...

Obtain the last used row in column A of an Excel sheet using Javascript Excel API by mimicking the VBA function `Range("A104857

Currently in the process of converting a few VBA macros to Office Script and stumbled upon an interesting trick: lastRow_in_t = Worksheets("in_t").Range("A1048576").End(xlUp).Row How would one begin translating this line of code into Typescript/Office Scr ...

Currently, I'm harnessing the power of TypeScript and React to identify and capture a click event on a dynamically generated element within my document

Is there a way to detect a click on the <p> tag with the ID of "rightDisplayBtn"? I've tried using an onclick function and event listener, but neither seem to be working as expected. function addDetails() { hideModal(); addBook ...

What is an improved method for defining a TypeScript type to store API method invocations?

Currently, I am exploring ways to enhance either GenericActionables or Items in a way that eliminates the need to manually add the API method name as a GenericActionables<"nameOfNewMethod"> to the Actionables type every time. Any suggesti ...

Guidelines for Managing Test Cases for a Promise-Returning Function with Resolve and Reject in Angular 6

I need to write a test case for a function that returns a Promise with resolve and reject. Here's the function definition: isAuthSuccess(): Promise<any> { const promise = new Promise((resolve, reject) => { if (this.userInfo) { ...

What is the best way for me to access a certain web address?

I am working on setting up a routing mechanism in my Angular project, but I'm encountering a URL routing error. The application is unable to locate the specified URL. Below is the routing setup: navigation.ts { id: 'documentation-manag ...

Converting JSON to TypeScript in an Angular project

Within my Angular project, I have an HTTP service that communicates with a web API to retrieve JSON data. However, there is a discrepancy in the naming convention between the key in the JSON response (e.g., "Property" in uppercase) and the corresponding pr ...

Create a combined string from a structure resembling a tree

In my web application, I have a type that defines the different routes available: interface IRoute { readonly path: string, readonly children?: IRoute[] } I want to create a union type containing all possible paths based on an IRoute object. Let&apos ...

How can I access members outside of a class without a name in Typescript?

I recently developed an "anonymous" class inspired by this insightful discussion on Typescript anonymous classes. However, I'm facing a challenge in accessing the outer scope members. Here's a snippet of my code for context: class BaseCounter { ...

Reset the select boxes when a button is clicked

I'm currently utilizing Devextreme within my Angular application, and I have three dx-selectbox elements in the component. I am attempting to clear all three dropdown selections when clicking a "clear" button. Unfortunately, I am unable to find a way ...

How do I implement data range filtering in Typescript?

Seeking assistance with filtering data by date range and forwarding the results to the client. The objective is to extract tickets created within specific dates, but I keep encountering a console error which is proving challenging to resolve. var befor ...

Utilizing Fullcalendar 5 in conjunction with Angular: Embedding Components within Events

Recently, my team made the transition from AngularJS to Angular 12. With this change, I upgraded Fullcalendar from version 3 to version 5 and started using the Angular implementation of Fullcalendar: https://fullcalendar.io/docs/angular While navigating t ...

Exploring TypeScript's Index Types: Introduction to Enforcing Constraints on T[K]

In typescript, we can utilize index types to perform operations on specific properties: interface Sample { title: string; creationDate: Date; } function manipulateProperty<T, K extends keyof T>(obj: T, propName: K): void { obj[propName] ...

Is there a way to verify a user's authorization status within Next.js 12.1.6 middleware?

I'm implementing a Nextjs middleware to redirect unauthenticated users to the login page. It's currently working locally, but not on the remote server: export async function middleware(req: NextRequest) { const { cookies } = req if (!cook ...

Injecting dependencies into an abstract class in typescript using Angular 2

So I have an abstract class that doesn't have a constructor, and my goal is to inject another class into it. Here's the abstract class: import { ErrorHandler } from '../../shared/services/errorHandler.service'; import { Inject } from ...

Deactivating PrimeNG checkbox

I am currently facing an issue with disabling a PrimeNG checkbox under certain conditions by setting the disabled property to true. However, whenever I click on the disabled checkbox, it refreshes the page and redirects me to the rootpage /#. To troublesh ...

After compilation, what happens to the AngularJS typescript files?

After utilizing AngularJS and TypeScript in Visual Studio 2015, I successfully developed a web application. Is there a way to include the .js files generated during compilation automatically into the project? Will I need to remove the .ts files bef ...