The type mismatch issue occurs when using keyof with Typescript generics

One of the challenges I am facing is related to an interface that stores a key of another interface (modelKey) and the corresponding value of that key (value):

interface ValueHolder<T, H extends keyof T> {
  modelKey: H;
  value: T[H];
}

My objective now is to store the horsePower property from the following model along with its specific type in a ValueHolder:

interface Car {
  id: number;
  horsePower?: number;
  date: Date;
};

This setup can be illustrated as follows:

const test: ValueHolder<Car, keyof Car> = {
  modelKey: 'horsePower',
  value: 1000,
};

Although there is no error at this point and the value gets stored successfully. The issue arises when a value of type Date is passed instead:

const test: ValueHolder<Car, keyof Car> = {
  modelKey: 'horsePower',
  value: new Date(),
};

The root cause behind this problem is that the value key seems to accept all types for any key in the provided model:

(property) ValueHolder<Car, keyof Car>.value: string | number | Date | undefined


Is there a way to restrict the value key in the ValueHolder interface so that it only allows values of type undefined or number when the modelKey is set to horsePower?

Interactive Demo Link

Answer №1

ValueHolder with the second generic argument H allows all keys, which in turn results in allowing all values.

To ensure illegal states are unrepresentable, you need to slightly modify your main utility type.

Take a look at this example:

type Values<T> = T[keyof T]

type ValueHolder<T> = Values<{
  [Prop in keyof T]: {
    modelKey: Prop;
    value: T[Prop]
  }
}>

// type Test = {
//     modelKey: "id";
//     value: number;
// } | {
//     modelKey: "horsePower";
//     value: number | undefined;
// } | {
//     modelKey: "date";
//     value: Date;
// } | undefined
type Test = ValueHolder<Car>

interface Car {
  id: number;
  horsePower?: number;
  date: Date;
};

// ok
const test: ValueHolder<Car> = {
  modelKey: 'horsePower',
  value: 1000,
};

// error 
const test2: ValueHolder<Car> = {
  modelKey: 'horsePower',
  value: new Date(),
};

Check out the Playground here

In the code snippet above, ValueHolder creates a union of all allowed values by iterating through each key and creating an interface like {Prop:{modelKey:P, value:T[P]}. Then, Values obtains each object's value {modelKey:P, value:T[P]} and makes a union of them.

UPDATE

Thanks, it works fine! What if I want to only allow T[Prop] to be of the type string? Is that possible?

Yes, it is possible. There are two ways to achieve this. You can restrict ValueHolder to only receive objects where the values are strings.

type ValueHolder<T extends Record<string, string>> = Values<{
  [Prop in keyof T]: {
    modelKey: Prop;
    value: T[Prop]
  }
}>

Alternatively, you can check inside the iteration whether T[Prop] is a string or not.

type ValueHolder<T> = Values<{
  [Prop in keyof T]: T[Prop] extends string ? {
    modelKey: Prop;
    value: T[Prop]
  } : never
}>

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

The ngtools/webpack error is indicating that the app.module.ngfactory is missing

I'm currently attempting to utilize the @ngtools/webpack plugin in webpack 2 to create an Ahead-of-Time (AoT) version of my Angular 4 application. However, I am struggling to grasp the output generated by this plugin. Specifically, I have set up a ma ...

The TSX file is encountering difficulty rendering an imported React Component

Upon attempting to import the Day component into the Week component (both .tsx files), an error is thrown: Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. ...

Using NestJS to inject a Factory provider into another Factory

I've gone through various questions here, but none of them addressed my issue: NestJS - Inject factory provider into another provider doesn't work I'm trying to set up an async provider that retrieves configurations from a remote repositor ...

Exploring the synergies between Typescript unions and primitive data types in

Given the scenario presented interface fooInterface { bar: any; } function(value: fooInterface | string) { value.bar } An issue arises with the message: Property 'bar' does not exist on type '(fooInterface | string)' I seem ...

Tips on downloading an image using the URL in nestjs

I'm trying to retrieve a link and save the associated image in the static folder, but I seem to be encountering some issues with my code. Here's what I have so far: @Injectable() export class FilesService { createFileFromUrl(url: string) { t ...

Display a semantic-ui-react popup in React utilizing Typescript, without the need for a button or anchor tag to trigger it

Is there a way to trigger a popup that displays "No Data Found" if the backend API returns no data? I've been trying to implement this feature without success. Any assistance would be greatly appreciated. I'm currently making a fetch call to retr ...

Utilizing Async/Await to Streamline Google Maps Elevation Requests

I'm struggling to run this in a sequential manner. I've experimented with various methods like using Promise.all and getting stuck in callback hell, but what I really need is to obtain elevations for each point that has a valid altitude value (no ...

Setting up React Context API with TypeScript: A Step-by-Step Guide

I recently updated my app.js file to app.tsx for improved functionality. However, I encountered an error with the current value. app.tsx import React, { createContext, useState } from "react"; import SelectPage from "./pages/select/select& ...

What steps should I take to establish a one-to-one relationship with legacy tables?

As I work on developing a web application (angular, nestjs, typeorm), I am faced with the challenge of linking two legacy user tables together within our existing system. Despite my efforts, I continue to encounter an error message related to column refere ...

Utilizing the componentDidUpdate method to update the state within the component

I have a scenario where two components are enclosed in a container. One component is retrieving the Id of a publication, while the other is fetching the issue date related to that specific publicationId. When I retrieve an Id, let’s say 100, it successf ...

The use of async/await within an observable function

I am looking to establish an observable that can send values to my observers. The issue lies in the fact that these values are acquired through a method that returns a promise. Is it possible to use await within the observable for the promise-returning f ...

The subscriber continues to run repeatedly, even after we have removed its subscription

The file brief-component.ts contains the following code where the drawer service is being called: this.assignDrawerService.showDrawer(parameter); In the file drawer-service.ts: private drawerSubject = new BehaviorSubject<boolean>(false); public ...

Retrieving values from objects using Typescript

I am facing an issue while trying to retrieve a value from an object. The key I need to use belongs to another object. Screenshot 1 Screenshot 2 However, when working with Typescript, I encounter the following error message. Error in Visual Studio Is ...

Angular Notification not visible

I have been attempting to display a notification after clicking a button using the angular-notifier library (version: 4.1.1). To accomplish this, I found guidance on a website called this. Despite following the instructions, the notification fails to app ...

Using the ternary operator will always result in a TRUE outcome

Having trouble with a ternary operator expression. AssociatedItemType.ExRatedTag ? session?.data.reloadRow(ids) : this.reloadItemRows(this.prepareItemsIdentities(ids)!), The AssociatedItemType is an enum. I've noticed that const ? 1 : 2 always retur ...

Guide on executing .ts script file and building angular 5 with NPM

I am facing an issue with running a file that has a .ts extension before executing npm run build to build my Angular 5 project. package.json "scripts": { "ng": "ng", "start": "ng serve", "compile": "npm-run-all myts build", "myts": "ts-no ...

What is the best way to obtain clear HTTP request data in a component?

My service retrieves JSON data from the backend: constructor(private http: Http) { }; getUsers(): Observable<any> { return this.http.get('http://127.0.0.1:8000/app_todo2/users_list'); }; In the component, this data is processed: ng ...

Issues with implementing Firebase Phone Authentication in Ionic 3

When trying to authenticate a phone number in Ionic 3 using Firebase, the program runs without error. However, after entering the phone number, nothing happens... The .html code is shown below: <ion-item> <ion-label stacked>Phone Number</i ...

What are some effective ways to exclude multiple spec files in playwright?

Within my configuration, I have three distinct projects. One project is responsible for running tests for a specific account type using one login, while another project runs tests for a different login. Recently, I added a third project that needs to run t ...

Changing the color of a specific span using Angular

I am working with a dynamic mat-table where columns are added and populated on the fly. The table headers are styled using divs and spans. My goal is to change the color of a header to black when clicked, but also un-toggle any previously selected header. ...