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

Having difficulty adjusting the configuration settings for an embedded report within Angular 7

While attempting to integrate a Power BI report with Angular 7, I encountered an unexpected error when trying to configure the settings of the report. The error message stated: Type '{ filterPaneEnabled: boolean; navContentPaneEnabled: boolean; }&apos ...

JS The clipboardData in ClipboardEvent is perpetually void

I am trying to retrieve files using CTRL+V from the ClipboardEvent in Angular6, but I am encountering an issue where the clipboardData is always empty regardless of whether I test images or text. This problem persists even when tested on the latest release ...

What is preventing typescript from inferring these linked types automatically?

Consider the following code snippet: const foo = (flag: boolean) => { if (flag) { return { success: true, data: { name: "John", age: 40 } } } return { success: false, data: null } ...

What is the most effective method for locating and modifying the initial instance of an element within a group?

In my Javascript/Typescript collection, I have the following items: [ {"order":1,"step":"abc:","status":true}, {"order":2,"step":"xyz","status":true}, {"order":3,"step":"dec","status":false}, {"order":4,"step":"pqr","status":false}, {"order":5,"step":" ...

Modify the Text Displayed in Static Date and Time Picker Material-UI

Looking to update the title text on the StaticDateTimePicker component? Check out this image for guidance. In the DOM, you'll find it as shown in this image. Referring to the API documentation, I learned that I need to work with components: Toolbar ...

When evaluating objects or arrays of objects to determine modifications

How can we detect changes in table data when users add input to cells? For example, if a user clicks on a cell and adds an input, the function should return TRUE to indicate that there are changes. If the user just clicks on the cell without making any ch ...

Incorporate a personalized Cypress function for TypeScript implementation

I'm in the process of developing a custom cypress command that will enable me to post a file using formData, as the current cy.request does not yet support formData. For the actual POST operation, I am utilizing request-promise-native. To begin with ...

In Angular 12, encountering the error "Unable to bind to 'formGroup' as it is not a recognized property of 'form'" is not uncommon

Encountering a problem that seems to have been addressed previously. Error: Can't bind to 'formGroup' since it isn't a known property of 'form'. I followed the steps by importing ReactiveFormsModule and FormsModule in the req ...

Switch statements in TypeScript may not function properly with type guards when assigning an object to a variable

I'm puzzled as to why the type guard is not working in the example provided below... Considering the following interfaces: interface ParamA { name: 'A'; aaa: boolean; } interface ParamB { name: 'B'; bbb: number; ...

Swap references between two components at the same level

There are two instances of custom-component spawned by a shared parent with distinct data, each displayed as a tab in the mat-tab-group. <mat-tab-group> <mat-tab label="TAB1"> <ng-template matTabContent> <custom-componen ...

Redux ConnectedProps will always have a type of never

I am facing an issue while attempting to connect a component to my Redux store following the steps outlined in the official documentation guide. The props that are connected seem to be coming through as type never. Here is a snippet of my code: Type defi ...

Does npm run use a separate version of TSC?

I am encountering an issue with my VS Code and Node.js project that uses Typescript. Within my package.json file's script block, there is an entry: "build-ts": "tsc" When I run simply tsc on the integrated terminal command line, the compilation proc ...

Tips for sorting through aggregated information in Foundry Functions

How can I filter on grouped data in Foundry Functions after grouping and aggregating my data? See the code snippet below for reference: @Function() public async grouping(lowerBound : Integer ): Promise<TwoDimensionalAggregation<string>> { ...

One way to update the value of the current array or object using ngModel in Angular 2 is to directly

I have a situation where I am dealing with both an array and an object. The array is populated with data retrieved from a service, while the object contains the first element of that array. feesEntries: Array<any> = []; selectedFeesEntry: any; clien ...

Unable to retrieve dynamically generated object property from an array in AngularJS 2+

Here is an example of an items array: this.itemList = [ { id: 1, name: 'a', address: 'as dasf a' }, { id: 2, name: 'b', address: 'as dasf a' }, { id: 3, name: 'c', address: 'as dasf a' } ]; ...

Is there a way to store session variables in Angular without the need to make an API call?

I am currently working with a backend in PHP Laravel 5.4, and I am looking for a way to access my session variables in my Angular/Ionic project similar to how I do it in my Blade files using $_SESSION['variable_name']. So far, I have not discove ...

Encountering a problem with Typescript and eslint while utilizing styled-components and Material UI: "Warning: React does not identify the `showText` prop on a DOM element."

While using a styled component to return a material ui Fab component, an error appears in the console: React does not recognize the `showText` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as low ...

Issue with data not refreshing when using router push in NextJS version 13

I have implemented a delete user button on my user page (route: /users/[username]) which triggers the delete user API's route. After making this call, I use router.push('/users') to navigate back to the users list page. However, I notice tha ...

Prevent selection of future dates and display them in a muted grey color in the p-calendar component

I am attempting to prevent users from selecting future dates and visually distinguish them by setting a grey color background. However, I am having trouble disabling the future dates while the grey color background is functioning correctly. Any ideas on ho ...

ng-select search functionality failing to display any matches

Currently, I am encountering an issue with the ng-select functionality in my Angular CLI version 11.2.8 project. Despite using ng-select version 7.3 and ensuring compatibility with the Angular CLI version, the search functionality within the select eleme ...