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

Several values are not equal to the typeorem parameter

I am looking for a way to create a Typeorm query that will return results similar to the following SQL statement: SELECT * FROM Users WHERE id <> 3 and id <> 8 and id <> 23; Any suggestions on how I can achieve this? Thank you! ...

"Utilize React input event handler for enforcing minimum input

Is there a way to validate the minimum length of an input without submitting the form using the onKeyDown event? I've attempted to do so, but it seems to ignore the minLength property. I have a simple input field that doesn't need a form. Here&ap ...

What is the most efficient way to apply multiple combinations for filtering the information within a table?

I'm facing an issue with my Angular project. I have 4 select boxes that allow users to apply different filters: office worker project name employee activities The problem I'm encountering is the difficulty in predicting all possible combination ...

Angular HttpInterceptor failing to trigger with nested Observables

Utilizing a HttpInterceptor is essential for adding my Bearer token to all calls made to my WebApi. The interceptor seamlessly functions with all basic service calls. However, there is one specific instance where I must invoke 2 methods and utilize the re ...

Ignore TypeScript errors when using TSNode with Mocha by forcing the compiler to emit code despite errors and having TSNode execute the emitted code

Below is a code snippet where the function test2 is invalid, but it should not affect testing of function test1: export function test1(): boolean { return true; } export function test2(): number { return "1"; } Test: import { assert as Assert } fr ...

What is the best way to prevent jest.mock from being hoisted and only use it in a single jest unit test?

My goal is to create a mock import that will be used only in one specific jest unit test, but I am encountering some challenges. Below is the mock that I want to be restricted to just one test: jest.mock("@components/components-chat-dialog", () ...

Sending an onclick event to a child class through React and TypeScript

I'm currently working through the Facebook React tutorial with Typescript for the first time. I need to pass an onClick event to the 'Square' component, which is implemented using Typescript and interfaces for state and props. How can I mod ...

What's the best way to include various type dependencies in a TypeScript project?

Is there a more efficient way to add types for all dependencies in my project without having to do it manually for each one? Perhaps there is a tool or binary I can use to install all types at once based on the dependencies listed in package.json file. I ...

The attribute 'getValue' is not a valid property for the data type 'Subject<boolean>'

Currently, I am working with Angular2 and have a BehaviorSubject. isOpen$: Subject<boolean> = new BehaviorSubject<boolean>(true); When I try to retrieve the latest value using: isOpen$.getValue() It functions correctly, however, there is a ...

The functionality of Layout.tsx is inconsistent across various pages

I'm having trouble with the console.log() code to display the page path only showing up in the "pages.tsx" file (src/app/pages.tsx) and not appearing in the console for other files located in (src/page/Login). Here is the code from layout.tsx: ' ...

Is it time to launch your React TypeScript application on AWS S3?

I need help transitioning my deployment from AWS S3 using JavaScript to TypeScript. What specific code should I incorporate in TypeScript to facilitate this transition? 1) I have downloaded files with a .ts extension. https://i.sstatic.net/He49G.jpg 2) H ...

The application is unable to load due to a console error showing that the endpoint is unreachable

Recently, I made an upgrade from Angular 5 to 7 while still keeping rxjs-compat in place. Initially, the application was running smoothly with no issues. However, we eventually decided to remove rxjs-compat and make the necessary changes. This is when we e ...

typescriptUsing redux: prevent parent component from accessing redux props

I'm currently using redux and typescript in my webapp project. What's the best approach for defining props in a component that receives redux-actions through @connect, as well as props from its parent? // mychild.tsx export namespace MyChildCom ...

Adjust the scroll bar to move in the reverse direction

I'm trying to modify the behavior of an overlay div that moves when scrolling. Currently, it works as expected, but I want the scroll bar to move in the opposite direction. For example, when I scroll the content to the right, I want the scroll bar to ...

Mastering the proper implementation of observables, async/await, and subscribing in Angular

I have a JSON file located at assets/constants/props.json. Inside this file, there is a key called someValue with the value of abc. The structure of the JSON file can be seen in the following image: https://i.stack.imgur.com/MBOP4.jpg I also have a serv ...

Angular HTTP post is failing on the first attempt but succeeds on the second try

Just started working on my first Angular exercise and encountered an issue where I am receiving an undefined value on the first attempt from an HTTP post request. However, on the second try, I am getting the proper response in Edge and Firefox. Thank you f ...

Encountering an Error When Trying to Run Npm Install in React-Native

I encountered an issue while trying to perform an npm install on my project, so I deleted the node modules folder in order to reinstall it. However, even after running npm install in the appropriate directory, I continued to face numerous errors in my term ...

Guide to Reverting the Two-Way ngModel Binding Data in Angular 2

I am utilizing a form in angular 2 that includes two-way binding data value ([(ngModel)]) to enable both edit and add functionality. When a user selects the edit option on the listing page and modifies the input, the new values automatically appear on the ...

An effective approach to positioning HTML elements at specific X and Y coordinates

I have an innovative project idea! The concept is to enable users to create points by clicking on the display. They can then connect these points by clicking again. However, I am facing a challenge when it comes to creating HTML elements at the exact loc ...

Unleashing the power of joint queries in Sequelize

Project.data.ts import { DataTypes, Model, CreationOptional, InferCreationAttributes, InferAttributes, BelongsToManyGetAssociationsMixin, BelongsToManySetAssociationsMixin, BelongsToManyAddAssociationsMixin } from 'sequelize'; import sequelize fr ...