Utilize TypeScript Generics to define an object with a different type specified for its key and value

I'm encountering some challenges when working with Generics in TypeScript.

My goal is to create an object based on another object type using Generics. I initially referenced this TypeScript documentation

This is the code snippet I have come up with so far:

type ClickableItem<T extends Record<string, any>, K extends keyof T> = {
  label: string;
  key: K;
  render?: (value: T[K]) => string;
   // The value (argument) type here should reflect the type from the selected key
};

The current issue

When attempting to use the ClickableItem type in an object, the argument type of render becomes a union of all possible type values. For example:


type Example = {
  a: string,
  b: number
}

const item = {
  label: 'x',
  key: 'a',
  render: (value) => value //ERROR HERE
} satisfies ClickableItem<Example, keyof Example>

The render method is being inferred incorrectly as type string | number, even though the key a is explicitly set to type string

In the previous example, the type of the render method should be (value: string)=> string instead of the inferred type

(value: number | string)=> string
.

Here's the TypeScript Playground link which showcases both the original example from TypeScript's documentation and my aforementioned test implementation

Answer №1

{
  label: 'x',
  key: 'a', 
  render: (value) => value
}

Does not fall under the category of

ClickableItem<Example, keyof Example>
, therefore it will not fulfill the requirement here.


ClickableItem<Example, keyof Example>
translates to this type:

type MyType = {
  label: string;
  key: 'a' | 'b'
  render?: (value: string | number) => string;
};

In simpler terms, your code is equivalent to:

const itemFromSatisfies = {
  label: 'x',
  key: 'a',
  render: (value) => value //ERROR HERE
} satisfies {
  label: string;
  key: 'a' | 'b'
  render?: (value: string | number) => string;
}

It's evident that there is no link between the key and the render function. This connection got lost because you provided keyof Example as K, resulting in one object type that would fit for all keys of T.

However, your object can't handle all keys of T. It can only manage the ones that are of type string since render should return a string, and you're just returning the raw value which must be a string.


Instead, what you need is a union that encompasses one member for each property.

type ClickableItemUnion<T extends Record<string, any>> = {
  [K in keyof T]: ClickableItem<T, K>
}[keyof T]


type MyTest = ClickableItemUnion<Example>
// ClickableItem<Example, 'a'> | ClickableItem<Example, 'b'>

This mapped type forms a union for every property of T where the key and render properties can be paired up accurately, one at a time.

Now, if you do this, it will work:

const itemFromUnion = {
  label: 'x',
  key: 'a', 
  render: (value) => value
} satisfies ClickableItemUnion<Example>

This works because this object aligns with the one member of the union where the key is 'a' and the render function accepts a string.


Therefore, based on all of that, we can simplify things back down by utilizing a mapped type that produces a union right from the start:

type ClickableItem<
  T extends Record<string, any>,
  K extends keyof T
> = {
  [P in K]: {
    label: string;
    key: P;
    render?: (value: T[P]) => string;
  }
}[K];

This type maps whatever union of keys is requested, yielding a union for each one.

Hence now:

type MyType = ClickableItem<Example, keyof Example>

is equivalent to:

type MyType = {
    label: string;
    key: "a";
    render?: ((value: string) => string) | undefined;
} | {
    label: string;
    key: "b";
    render?: ((value: number) => string) | undefined;
}

And thus, it can be used in your satisfies constraint without any issues:

const item = { // fine
  label: 'x',
  key: 'a', 
  render: (value) => value
} satisfies ClickableItem<Example, keyof Example>

Visit 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

Can you define the type of binding value in AngularJS 1.5(6) using TypeScript?

I am looking to define the type of binding items so that I am able to utilize components similar to functions. For instance, consider a component: angular.module('app').component('navBar', new NavBar()); class NavBar{ public bin ...

Concealing a VueJs component on specific pages

How can I hide certain components (AppBar & NavigationDrawer) on specific routes in my App.vue, such as /login? I tried including the following code in my NavigationDrawer.vue file, but it disables the component on all routes: <v-navigation-drawer ...

Unable to find solutions for all parameters needed by a CustomComponent within Angular

Whenever I attempt to compile the project for production, an error pops up: There is a problem resolving all parameters for EmployeeComponent in C:/.../src/app/employee/employee.component.ts: (?, ?, ?). Oddly enough, when I run the application, every ...

Having trouble resolving parameters? Facing an Angular dependency injection problem while exporting shared services?

Seeking to streamline the process of importing services into Angular 4 components, I devised a solution like this: import * as UtilityService from '../../services/utility.service'; As opposed to individually importing each service like so: imp ...

Tips for effectively utilizing Mongoose models within Next.js

Currently, I am in the process of developing a Next.js application using TypeScript and MongoDB/Mongoose. Lately, I encountered an issue related to Mongoose models where they were attempting to overwrite the Model every time it was utilized. Here is the c ...

Why am I receiving an undefined value?

I am currently engaged in Angular4 development and have encountered an issue that I cannot seem to resolve. The problem arises when I attempt to store a value on the service provider and retrieve it from a component. Below is a snippet of my code: Service ...

Encountering an error while receiving a response for the Update API request

Recently, I ventured into the world of swagger and decided to test it out with a small demo project in node-js. I successfully created 5 APIs, but encountered an issue specifically with the PUT API. Surprisingly, when testing it on Postman, everything work ...

Guide to configuring a not null property in Typescript Sequelize:

Hello there! I am trying to figure out how to set a not null property using TypeScript Sequelize. I have tried using the @NotNull decorator, but unfortunately it does not seem to be working. The errors I am encountering are as follows: Validation error: W ...

Testing Angular Components: Ensuring Proper Unit Testing of Public Members Intended for HTML Input Only

How can Angular's ng test --code-coverage help with unit testing public variables that are strictly used as direct input in HTML? https://i.sstatic.net/z6j1O.png These variables are eventually placed into other components like this: <ctrl-grid ...

Converting the source to your image assets in Angular: A step-by-step guide

I am looking to update the source code. Here is an example: <div *ngFor="let item of meal.allergenList" class="btn btn-primary"> <img [src]="item" alt=""> </div> I want to make the following co ...

Utilizing TypeScript union types in React: A step-by-step guide

I'm currently working on applying types to ReactJS props using an interface that includes a union type. In the example below, you can see that the tags type is a union type. export interface TagInterface { id: number; name: string; } export inter ...

Accessing information independent of Observable data in TypeScript

When attempting to send an HttpRequest in Typescript, I encountered an issue where the received data could not be stored outside of the subscribe function. Despite successfully saving the data within the subscribe block and being able to access it there, ...

Mock a single method within an Angular service

Within my Angular service, I have a method that listens for state changes and returns an observable. However, other methods within the same service handle transformation logic: ngOnInit() { this.isLoading = true; this.myService.stateListener().sub ...

Determining type properties dynamically depending on the value of another property

My goal is to create a type that ensures the correct usage of the DynamicColor type. enum ColorsEnum { red = "red", green = "green", blue = "blue", yellow = "yellow", } type ColorsMapperType = { type: Colo ...

What is the best approach to validating GraphQL query variables while utilizing Mock Service Worker?

When simulating a graphql query with a mock service worker (MSW), we need to verify that the variables passed to the query contain specific values. This involves more than just type validation using typescript typings. In our setup, we utilize jest along ...

Refining Angular service coding techniques

In my application, I have implemented this specific format to interact with an API and retrieve data from it. Below is the code snippet taken from one of the service.ts files: getCheckoutDetails(): Observable<UserDetail> { let query = `7668`; ...

"NODEJS: Exploring the Concept of Key-Value Pairs in Object

I am facing a challenge with accessing nested key/value pairs in an object received through a webhook. The object in req.body looks like this: {"appId":"7HPEPVBTZGDCP","merchants":{"6RDH804A896K1":[{"objectId&qu ...

How can I use a string variable in Angular 2 to create a dynamic template URL

@Component({ selector: 'bancaComponent', templateUrl: '{{str}}' }) export class BancaComponent implements OnInit { str: String; constructor(private http: Http) { } ngOnInit(): void { this.str = "./file.component.html"; } An ...

What is the correct approach to managing Sequelize validation errors effectively?

I am working on a basic REST API using Typescript, Koa, and Sequelize. If the client sends an invalid PUT request with empty fields for "title" or "author", it currently returns a 500 error. I would prefer to respond with a '400 Bad Request' ins ...

Displaying the size of a group in a tooltip with Highcharts Angular

Currently, I am utilizing dataGrouping to group data in my chart based on dates along the x-axis. My goal is to display the group size in the tooltip similar to what was shown in this example (click on "show more info," then open the sequence chart, wait f ...