What causes the return value of keyof to vary in this particular case?

type AppleNode = {
  type: 'Apple'
  name: string
  score: number
}

type BananaNode = {
  type: 'Banana'
  id: number
  score: number
}
type FruitNodes = AppleNode | BananaNode

type fruitTest = {
  [P in keyof FruitNodes]: 21
}
// This will result in:
type fruitTest = {
    type: 21;
    score: 21;
}

However, using generics gives a different outcome

type genericTest<T> = {
  [P in keyof T]: 21
}
type genericTest2 = genericTest<FruitNodes>

genericTest2 now looks like this: type genericTest2 = genericTest | genericTest

View on playground

Answer №1

There is some awareness among people about conditional types distributing over unions, but what is lesser known is that mapped types also exhibit distributive behavior in specific scenarios.

Directly utilizing Nodes does not result in distribution. For example:

type foo3 = {
  [P in keyof Nodes]: 'foo'
}
// foo3 is equivalent to 
// type foo3 = {
//     type: "foo";
//     flag: "foo";
}

This happens because keyof Nodes only captures the common properties of the union, leading to a type with shared properties from both union constituents.

When a mapped type operates on keyof T and T is a type parameter instantiated with a union, the distributive nature of mapped types emerges. This means the mapped type is applied individually to each element of the union, which are then united to create the final output. Essentially,

foo<Nodes> = foo<NodeA | NodeB> = foo<NodeA> | foo<NodeB>
.

This behavior is logical for many types and usually easy to grasp. Mapped types like Readonly<T> demonstrate distributiveness; when applied to a union, it results in a readonly union rather than just the overlapping properties.

If you wish to make a simple type distributive, you can introduce a type parameter through a conditional type:

// Distributive 
type foo3 = Nodes extends infer T ? {
//    ^?
  [P in keyof T]: 'foo'
}: never

Alternatively, to avoid distribution, you can map over something other than keyof T:

// Non-distributive
type foo<T> = {
  [P in Exclude<keyof T, never>]: 'foo'
}

Playground Link

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

Tips for preventing repetition in http subscribe blocks across various components

Imagine a scenario where there is a service used for making HTTP request calls. There are two different components (which could be more than two) that need to send the same request using the same observables via this service. After receiving the result, it ...

How do AppComponent and @Component relate to each other in AngularJs 2?

Recently, I came across the file app.component.ts in Example and found some interesting code. The link to the example is: here. Here's a snippet of the code: import { Component } from '@angular/core'; export class Hero { id: number; na ...

What is the best way to set up TypeScript interfaces using predefined string literals to limit the possible data types for shared attributes?

In this scenario, we have two interfaces named A and B with validation and variant properties. The goal is to create an Example object by using only the variant and validation values provided (since field is already defined). However, I encountered an erro ...

When defining a GraphQL Object type in NestJS, an error was encountered: "The schema must have unique type names, but there are multiple types named 'Address'."

Utilizing Nestjs and GraphQL for backend development, encountered an error when defining a model class (code first): Schema must contain uniquely named types but contains multiple types named "Address". Below is the Reader model file example: @ObjectType() ...

Tips for preventing the need to convert dates to strings when receiving an object from a web API

I am facing an issue with a class: export class TestClass { paymentDate: Date; } Whenever I retrieve an object of this class from a server API, the paymentDate field comes as a string instead of a Date object. This prevents me from calling the ...

Struggling to find the definition of a Typescript decorator after importing it from a separate file

Consider the following scenario: decorator.ts export function logStuff(target: Object, key: string | symbol, descriptor: TypedPropertyDescriptor<any>) { return { value: function (...args: any[]) { args.push("Another argument ...

In TypeScript and React, what is the appropriate type to retrieve the ID of a div when clicked?

I am facing an issue in finding the appropriate type for the onClick event that will help me retrieve the id of the clicked div. const getColor = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => { const color = event.target.id; // ...

The initial Get request does not receive data upon first attempt

In the process of developing an Angular project, I am faced with the task of retrieving data from my backend by making requests to an API. However, before the backend can fetch the required data, certain parameters must be sent through a post request. Once ...

Issue with Ionic Native File: File.writeFile function - file is not being created and there is no callback response

I've exhausted all the different solutions I could find, but unfortunately, the file isn't getting saved and nothing seems to be happening. The callback functions aren't being called - neither success nor error. Here are the solutions I&apo ...

Exploring properties of nested elements in React

Picture a scenario where a specific element returns: <Component1> <Component2 name="It's my name"/> </Component1> Now, what I want to accomplish is something like this: <Component1 some_property={getComponent2'sN ...

"Using Angular and TypeScript to dynamically show or hide tabs based on the selected language on a website

When switching the language on the website, I want to display or hide a specific tab. If the language is set to German, then show the tab; if any other language is selected, hide it. Here's my code: ngOnInit(): void { this.translate.onLangChange.s ...

Can you please provide a step-by-step guide for using socket.io with TypeScript and webpack, after installing it

Currently, I am experimenting with TypeScript in conjunction with node.js, socket.io, and webpack. To facilitate this setup, I have configured the webpack.config.js, tsconfig.json, and tsd.json files. Additionally, I acquired the typings file for socket.i ...

executing afterEach in multiple specification files

Seeking a solution to execute shared code across tests in various spec files in Playwright using TypeScript. Specifically, I need to upload test results based on the testInfo. While I know that fixtures can achieve this, it's not the most efficient op ...

Can the child component ensure that the Context value is not null?

In the process of developing a Next.js/React application with TypeScript, I've implemented a UserContext in pages/_app.js that supplies a user: User | null object to all child components. Additionally, there's a ProtectedRoute component designed ...

The exploration of child routes and modules

I'm currently working on a somewhat large project and I've decided to break it down into modules. However, I'm facing an issue with accessing the routes of admin.module.ts. In my app.module, I have imported the admin Module. imports: [ Br ...

Define the static property as an array containing instances of the same type

I created a class called Foo with a static property named instances that holds references to all instances. Then, I have another class called Bar which extends Foo: class Foo { static instances: Foo[]; fooProp = "foo"; constructor() { ...

Linking to a file within an npm package

Is it possible to reference local files within an npm package? Will these references still work correctly when the package is installed by a different consumer? For example, let's say I have developed an npm package for Angular which includes some HTM ...

Looking to execute a service method within an authguard service?

I am a beginner with Angular and I am looking to invoke a service method within the authguard service. The specific service function that I need is as follows. Please note that I do not want to make any changes to this service function. loadOrganizations() ...

JavaScript - Cannot access the 'name' property on an empty object

I am currently following a React tutorial that demonstrates how to create a useForm hook for linking form input to state. Here is the implementation of the hook: const useForm = (initial = {}) => { const [inputs, setInputs] = useState(initial) ...

Cross-origin error triggered by using an external API within a ReactJS application

I'm facing a common CORS issue in my application while trying to call an API. The API I am attempting to access can be found here: https://github.com/nickypangers/passport-visa-api Below is the code snippet used for making the API call: const getVi ...