The key is not applicable for indexing the type as expected

Here is the TS code I am working with:

type Fruit = { kind: "apple" } | { kind: "grape"; color: "green" | "black" };
type FruitTaste<TFruit extends Fruit> = TFruit["kind"] extends "apple"
  ? "good"
  : TFruit["color"] extends "green"
  ? "good"
  : "bad";

Playground link

When trying to access TFruit["color"], it throws an error saying:

Type '"color"' cannot be used to index type 'TFruit'.

However, this should not be an issue as we are on a part where TFruit is limited to only

{ kind: "grape"; color: "green" | "black" }
and the key color should exist.

Interestingly, TypeScript does not have any problem with the "runtime" version of this code:

type Fruit = { kind: "apple" } | { kind: "grape"; color: "green" | "black" };
const fruitTaste = (fruit: Fruit) =>
  fruit.kind === "apple" ? "good" : fruit.color === "green" ? "good" : "bad";

Playground link

Why is this happening? How can I implement the FruitTaste type correctly?

Answer №1

In my opinion, one way to implement this is by separating the different types such as { kind: "apple" } and

{ kind: "grape"; color: "green" | "black" }
.

type Apple = { kind: "apple" };
type Grape = { kind: "grape"; color: "green" | "black" };
type Fruit = Apple | Grape;
type FruitTaste<TFruit extends Fruit> = TFruit extends Grape
    ? TFruit["color"] extends "green"
        ? "good"
        : "bad"
    : "good";

Answer №2

When asking why, the answer lies in TypeScript's approach to handling unions in generic conditionals based on specific properties. The issue arises when TFruit cannot be indexed due to it still being a union with elements where one lacks a color property.

To circumvent this, my solution would resemble the following:

type FruitTaste<TFruit extends Fruit> = TFruit extends { kind: "apple" }
  ? "good"
  : (TFruit & { color: string })["color"] extends "green"
    ? "good"
    : "bad";

Prior to accessing color, we can utilize an intersection to enforce TypeScript's acknowledgment of this required property.

Interactive Code Playground

Answer №3

Here's a clever solution from Matt Pocock on Twitter:

TFruit["color"] extends "green"

doesn't properly differentiate the union, however

TFruit extends { color: 'green' }

does, and it compiles correctly:

type Fruit = { kind: "apple" } | { kind: "grape"; color: "green" | "black" };
type FruitTaste<TFruit extends Fruit> = TFruit["kind"] extends "apple"
  ? "good"
  : TFruit extends { color: 'green' }
  ? "good"
  : "bad";

Access Playground here

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

Error message: The property 'data' is not recognized within this context. Additionally, the property 'datatime' does not exist on the current type

I'm currently working on generating a graph using Firestore data and ng2charts. However, when I run my code, I encounter the following errors: Property 'data' does not exist on type 'unknown', causing an error in item.data Simila ...

Ways to verify the identity of a user using an external authentication service

One of my microservices deals with user login and registration. Upon making a request to localhost:8080 with the body { "username": "test", "password":"test"}, I receive an authentication token like this: { "tok ...

What is the best way to filter specific data types when using ngFor in Angular?

As I loop through the array named "fruits," which contains objects of type "FruitService" that I created, I want to display each element. However, when I delete them (I know it's strange, no database involved), they turn into type "undefined" and star ...

In order to effectively manage the output of these loaders, it may be necessary to incorporate an extra loader. This can be achieved by using the

I'm currently working with react typescript and trying to implement a time zone picker using a select component. I attempted to utilize the npm package react-timezone-select, but encountered some console errors: index.js:1 ./node_modules/react-timezo ...

Angular UI validation malfunctioning upon loading of the page

My webpage contains multiple rows with specific validation requirements - at least one Key, Time, and Input must be selected. Initially, the validation works as expected. However, after saving and reloading the page, the default selection for Key, Time, an ...

Issue with Typescript Conditional Type not being functional in a function parameter

For a specific use-case, I am looking to conditionally add a key to an interface. In attempting to achieve this, I used the following code: key: a extends b ? keyValue : never However, this approach breaks when a is generic and also necessitates explicit ...

What causes the createResource error to become undefined when it is refetched before being properly set?

How can I access and display the error property for a createResource? In this scenario, why is the error initially set to undefined before it is thrown? The logging shows that it first displays undefined for the error before eventually getting to line er ...

Having trouble accessing previously submitted form values in Angular

When I try to update the form, I notice that my meetupform.controls.day array is not retaining the previously selected values app.component.html <div *ngIf="meetupForm.controls.recurring.value==='weekly'"> <mat-checkbox (change)="o ...

Diverse behaviors exhibited by an array of promises

I've developed a function that generates an array of promises: async addDefect(payload) { this.newDefect.setNote(payload.note); this.newDefect.setPriority(payload.priority); const name = await this.storage.get(StorageKeys.NAME); ...

Unable to properly display date formatting in AG-Grid using the Angular date pipe

Currently, I am utilizing ag-grid in conjunction with Angular 8. Within my table, there is a column where my intention is to exhibit dates in a concise format. In order to achieve this, I opted to utilize the Angular date pipe. However, it appears that the ...

lint-staged executes various commands based on the specific folder

Within my project folder, I have organized the structure with two subfolders: frontend and backend to contain their respective codebases. Here is how the root folder is set up: - backend - package.json - other backend code files - frontend - p ...

Converting JSON data into TypeScript interface objects within an Angular 2 environment

I have a JSON dataset with the following structure: { "timestamp": 1467471622, "base": "USD", "rates": { "AED": 3.673027, "AFN": 68.475, "ALL": 123.095199, "AMD": 476.8075, "ANG": 1.78385, "AOA": 165.846832, "ARS": 15.05 ...

The module is missing a declaration file and therefore has an implicit type of 'any'. This error (TS7016) occurs in TypeScript version 2.0

So I've been experimenting with the module react-image-gallery. Surprisingly, there seems to be no types available for this package when trying to install it using npm. When attempting npm install @types/react-image-gallery, all I get is a 404 error. ...

SvgIcon is not a recognized element within the JSX syntax

Encountering a frustrating TypeScript error in an Electron React App, using MUI and MUI Icons. Although it's not halting the build process, I'm determined to resolve it as it's causing issues with defining props for icons. In a previous pro ...

Error in Angular 2 after transition to @types: encountering "require" name not found issue

After transitioning my project from old typings to types-publisher, I have successfully resolved most of my dependencies. However, I am consistently encountering an error that reads Cannot find name 'require'. Below is a snippet from my tsconfig. ...

The Angular 5 lifecycle hook ngOnChanges is triggered just once in a child component

I am struggling with syncing a child component containing complex input (such as chart data) with the API response received in the parent component in Angular 5. I am using the @Input() decorator to pass the chart data from the parent to the child componen ...

Methods for setting a global instance within a local function

Hello, I'm fairly new to using TypeScript and Angular2. Let me quickly explain what I'm trying to accomplish with my method. I have some hard-coded XML data that I need to parse into JSON format. Once parsed, each JSON object needs to be assigned ...

Getting event properties in a React component using the rest operator: A comprehensive guide

Can someone please assist me? I am new to TypeScript and struggling with how to use event props in my component. I have defined two props and need all my events as rest props. I encountered an error when trying to use my component with onClick event. The ...

Adding TypeScript types to an array within a function parameter: A step-by-step guide

Having some trouble defining the array type: The code below is functioning perfectly: const messageCustomStyles: Array<keyof IAlertMessage> = [ 'font', 'margin', 'padding' ]; r ...

How to Retrieve an Array from a Promise Using Angular 4 and Typescript

I am encountering difficulties when trying to store data from a returned promise. To verify that the desired value has been returned, I log it in this manner: private fetchData() { this._movieFranchiseService.getHighestGrossingFilmFranchises() ...