Tips for reducing a discriminated union by using the key value of a Record

I am working with a union type that has a discriminator property called "type". I then define a Record<> where the keys are the "type" property values.

How can I specify the second generic parameter of the Record (Record< , this param>) so that the values in the map/record are inferred based on the corresponding key?

TypeScript Playground

interface Dog {
  type: 'dog';
  breed: string;
}

interface Vehicle {
  type: 'vehicle';
  max_speed: number;
};

type Thing = Dog | Vehicle;

const printMap: Record<Thing['type'], (thing: Thing) => string> = {
  'dog': x => `Dog ${x.breed}`,
                    // ^^^^^ Property 'breed' does not exist on type 'Thing'. Property 'breed' does not exist on type 'Vehicle'.
                    // How do I make TypeScript infer `x` is a Dog because the key/property name is 'dog'?
  'vehicle': x => `Vehicle max speed ${x.max_speed}`
                                      // ^^^^^^^^^ Property 'max_speed' does not exist on type 'Thing'. Property 'max_speed' does not exist on type 'Dog'.
                                      // How do I make TypeScript infer `x` is a Vehicle because the key/property name is 'vehicle'?
}

Answer №1

To address this issue, you cannot utilize a Record<K, V> since it requires the same type for all property values. For distinct types for each property value, you need to create a mapped type that selects the appropriate member from the discriminated union for each property. Here is a possible solution:

type DiscriminatedUnionHandler<T extends Record<K, PropertyKey>, K extends keyof T, R> =
  { [P in T[K]]: (foo: Extract<T, Record<K, P>>) => R }

The

DiscriminatedUnionHandler<T, K, R>
function accepts a discriminated union type T, its discriminant key name K, and the return type R for individual handler functions. The resulting type includes properties for each value of T[K] (in this case,
"dog" | "vehicle"
for Thing), where each property represents a function that takes the relevant member of T</code and returns <code>R.

For the Thing scenario, the implementation would be as follows:

type DiscriminatedUnionHandler<T extends Record<K, PropertyKey>, K extends keyof T, R> =
  { [P in T[K]]: (foo: Extract<T, Record<K, P>>) => R }

interface Dog {
  type: 'dog';
  breed: string;
}
interface Vehicle {
  type: 'vehicle';
  max_speed: number;
};

type Thing = Dog | Vehicle;

const printMap: DiscriminatedUnionHandler<Thing, "type", string> = {
  'dog': x => `Dog ${x.breed}`,
  'vehicle': x => `Vehicle max spped ${x.max_speed}`
}

Link to Playground for code testing

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

Effectively managing intricate and nested JSON objects within Angular's API service

As I work on creating an API service for a carwash, I am faced with the challenge of handling a large and complex json object (referred to as the Carwash object). Each property within this object is essentially another object that consists of a mix of simp ...

Having trouble deleting JavaScript object properties within a loop?

Struggling to comprehend the behavior of this particular piece of javascript code. const devices = searchResult.results.forEach(device => { const temp = Object.keys(device.fields); for(var property in temp) { if(device.fields.hasOwnPro ...

Exploring nested JSON objects within an array using ngFor directive

My application uses Angular 6 and Firebase. I am trying to showcase a list of all appointments. Below is my approach: service.ts getRDV() { this.rdvList = this.firebase.list('/rdv'); return this.rdvList; } Model: export class RDV { key: ...

After selecting an item, the Next UI navbar menu seems to have trouble closing

Having trouble with the navbar menu component not closing when an option is selected from the menu. The menu does open and close successfully within the menu icon. I attempted to use onPress() but it doesn't seem to be working as expected. "use c ...

Utilizing the useSelect hook in Typescript to create custom types for WordPress Gutenberg, specifically targeting the core/editor

As I delve into development with WordPress and the Gutenberg editor, my goal is to incorporate TypeScript into the mix. However, I encounter a type error when trying to utilize the useSelect() hook in conjunction with an associated function from the core/e ...

does not have any exported directive named 'MD_XXX_DIRECTIVES'

I am currently learning Angular2 and I have decided to incorporate angular material into my project. However, I am encountering the following errors: "has no exported member MD_XXX_DIRECTIVES" errors (e.g: MD_SIDENAV_DIRECTIVES,MD_LIST_DIRECTIVES). Her ...

Trouble with invoking a function within a function in Ionic2/Angular2

Currently, I am using the Cordova Facebook Plugin to retrieve user information such as name and email, which is functioning correctly. My next step is to insert this data into my own database. Testing the code for posting to the database by creating a func ...

The JavaScript code is executing before the SPFX Web Part has finished loading on the SharePoint page

I recently set up a Sharepoint Page with a custom masterpage, where I deployed my SPFx Webpart that requires certain javascript files. While the Webpart functions correctly at times, there are instances when it doesn't work due to the javascript bein ...

Ways to determine the current active tab in React are:

Currently, I am facing an issue with my code involving two tabs. Upon clicking on the second tab, I want to display a specific message. However, I am struggling to determine when the second tab is selected. The main problem lies in the fact that the selec ...

Issue: The key length and initialization vector length are incorrect when using the AES-256-CBC encryption algorithm

Within my coding project, I have developed two essential functions that utilize the AES-256-CBC encryption and decryption algorithm: import * as crypto from "crypto"; export const encrypt = (text: string, key: string, iv: string) => { con ...

Issue encountered: "TypeError: .... is not a function" arises while attempting to utilize a component function within the template

Within my component, I am attempting to dynamically provide the dimensions of my SVG viewBox by injecting them from my bootstrap in main.ts. import {Component} from 'angular2/core'; import {CircleComponent} from './circle.component'; i ...

Error: The function cannot be executed on an immutable object in React Native Jest unit testing with Typescript

Currently, I am experimenting with jest unit testing for a typescript-based react native project. However, I am facing an issue when I run npm test, and the error message is as follows: ● Test suite failed to run TypeError: seamless_immutable_1.default ...

Is it possible to declare language features in Typescript? For example, changing `!variable` to `no variable`

Can Typescript language features be declared within the app's source code? I want to enhance code clarity by implementing a small feature. Modified Null Test if (no userDetails) { // handle null } This new null test syntax is a little more conc ...

Verification based on conditions for Angular reactive forms

I am currently learning Angular and working on creating a reactive form. Within my HTML table, I have generated controls by looping through the data. I am looking to add validation based on the following cases: Upon page load, the Save button should be ...

The identifier "id" is not a valid index for this type

The code snippet below demonstrates the similarities and differences between the functions addThingExample2 and addThing. While addThingExample2 directly uses the union type Things, addThing utilizes a generic parameter THING extends Thing. The expression ...

Troubleshooting issues with TypeScript D3 v4 module import functionality

As I embark on the journey of creating a miniature JS library using D3 to visualize line charts, I find myself navigating unfamiliar waters. However, I believe that deep diving into this project is the most effective way for me to learn. Below is the cont ...

Designing a web application with Angular2

As a newcomer to Angular2, I have recently started working on creating a simple hello world application. I have come across the concept of Modules and Components in Angular2. My main source of confusion lies in how to properly design an Angular2 applicat ...

What is the best way to implement a dispatch function in TypeScript?

Despite my expectations, this code does not pass typechecking. Is there a way to ensure it is well typed in Typescript? const hh = { a: (_: { type: 'a' }) => '', b: (_: { type: 'b' }) => '', } as const; ex ...

The TypeScript optional callback parameter is not compatible with the anonymous function being passed to it

Encountering an issue with TS callbacks and function signatures. Here is my scenario: ... //inside a class //function should accept a callback function as parameter refreshConnection(callback?: Function) { //do something //then ca ...

Enhance the MUI palette by incorporating TypeScript, allowing for seamless indexing through the palette

When utilizing the Material UI Palette with Typescript, I am encountering a significant issue due to limited documentation on MUI v5.0 in this area. Deep understanding of typescript is also required. The objective is to iterate through the palette and vir ...