What is the reason behind the narrowing of the type by indexing into a mapped type?

This question is inspired by an amazing answer found here:

My curiosity lies in why the indexing works in the mapped type trick.

Let's illustrate with an example:

type MyData = {
  a: {
    alpha: string; 
  }; 
  b: {
    beta: number; 
  }
}

type NotNarrowPayload<T extends MyData> = {
  key: keyof T; 
  fn: (data: T[keyof T]) => void; 
}

type NarrowDataPayload<T extends MyData> = {
  [K in keyof T]: {
    key: K; 
    fn: (data: T[K]) => void; 
  }
}[keyof T]; 


function acceptsMTVTFunction<T extends MyData>(baseData: T,fn: NotNarrowPayload<T>) {

}


function acceptsMTVTFunction2<T extends MyData>(baseData: T,fn: NarrowDataPayload<T>) {

}



acceptsMTVTFunction(
    {
    a: {
      alpha: "a"
    }, 
    b: {
      beta: 99
    },
  }, 
  {
    key: "a", 
    fn: (data) => {
      // TypeScript doesn't match the key to the value type, and this makes sense. 
      // (parameter) data: {
      //     alpha: string;
      // } | {
      //     beta: number;
      // }
  }
}
); 



acceptsMTVTFunction2(
  {
    a: {
      alpha: "a"
    }, 
    b: {
      beta: 99
    },
  }, 
  {
    key: "a", 
    fn: (data) => {
      //       (parameter) data: {
      //     alpha: string;
      // }
  }
}
);

TypeScript Playground

To clarify - I acknowledge that the first approach doesn't yield the expected results.

However, what puzzles me is why does the second approach does work.

If we delve deeper into this:

type NarrowDataPayload<T extends MyData> = { // Declaring an object 
  [K in keyof T]: { // For each key in T, a property is defined
    key: K; // The values are dependent on T and K 
    fn: (data: T[K]) => void; 
  }
}[keyof T]; // Only retaining types accessible by keyof T. 

The mystery is - how does TypeScript discern that the key type used here is "a" instead of "b."

Answer №1

When dealing with TypeScript, the challenge lies in distinguishing key types within an object. Take a look at how the NarrowDataPayload is resolved:

const data = {
  a: {
    alpha: "a"
  },
  b: {
    beta: 99
  }
}; 


type A = NarrowDataPayload<typeof data>; 

// type A = {
//     key: "a";
//     fn: (data: {
//         alpha: string;
//     }) => void;
// } | {
//     key: "b";
//     fn: (data: {
//         beta: number;
//     }) => void;
// }

This results in a union type.

Utilizing the index into the type mapping technique helps align the key and fn, ultimately enhancing the previous approach's efficiency.

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 implementation of Typescript in Express does not rely on Middleware

I've encountered an issue with my Auth Middleware - it seems that the middleware isn't being called at all. Even when I intentionally throw an Error within the middleware function, nothing is printed out. For testing purposes, I only need to inv ...

Show variously colored information for each selection from the dropdown using Angular Material

I am trying to change the color of displayed content based on user selection in an Angular application. Specifically, when a user chooses an option from a dropdown list, I want the displayed text to reflect their choice by changing colors. For example, i ...

Troubleshooting Paths with Angular's NgFor Directive

Within my Angular project, I have implemented a basic ngFor loop to display logo images. Here is a snippet of the code: <div *ngFor="let item of list" class="logo-wrapper"> <div class="customer-logo"> & ...

Exploring the best practices for utilizing the error prop and CSS with the Input API in Material UI while utilizing context

When working with the MUI Input API props and CSS, I encountered an issue related to the {error} and its use. This is how my code looks: const [value, setValue] = useState<string>(cell.value); const [startAdornment, setStartAdornment] = useState< ...

Error encountered while attempting to load an image in a React component using TypeScript linting

I am currently working on a React app with Next.js where I have imported an image using the following code: import image1 from '../../../img/dummy-image-2.jpg'; Subsequently, I use the image in my app like so: <img src={image1} alt="Dumm ...

Troubleshooting: Resolving JSX props issue in Vue template

Ever since integrating the FullCalendar library into my Vue project, I've been encountering an error every time I try to use my custom component in a Vue template. My setup includes Vue 3, Vite, VSCode, eslint, and prettier. This issue seems to be m ...

The TypeScript compiler encounters difficulties in locating type definitions when utilizing an inherited tsconfig file

There seems to be an issue with the functionality of tsconfig.json inheritance, specifically regarding the proper inheritance of the "typeRoots" setting. http://www.typescriptlang.org/docs/handbook/tsconfig-json.html (folder structure provided below) we ...

What is the reason behind the restriction on using 'this' on the left side of an assignment?

Within the component class, I've been working on this: export class myapp { detail; myarr = ['me', 'myself', 'i']; title = this.myarr[0]; this.detail = this.title ; //error } I'm curious why `this.detail` ...

TypeORM: When generating a migration, a SyntaxError is thrown stating that an import statement cannot be used outside a

While configuring TypeORM in my NextJS TypeScript project, I encountered an issue where I received the error message: SyntaxError: Cannot use import statement outside a module when attempting to create migrations for my entities. ...

Struggling with getting render props to work in Next.js version 13

Looking to develop a custom component for Contentful's next 13 live preview feature in the app directory, I thought of creating a client component that can accept a data prop and ensure type safety by allowing a generic type to be passed down. Here is ...

Create a union type by utilizing indices of an array type

For instance: type BasicTheme = { name: 'basic'; colors: [string, string]; }; type AdvancedTheme = { name: 'advanced'; colors: [string, string, string, string]; }; type MainColor = ???; // 'main-1' | 'main-2&apo ...

I attempted to retrieve the id field of a jsonwebtoken following the user's login, but unfortunately, I was unable to access it

While working on the backend of my application, I encountered an issue trying to save the id field from a JSON Web Token (JWT) to an item that I created after a user logs in. Although I am able to log the JWT information successfully, I'm facing diffi ...

How can I customize the visibility toggles for the password input field in Angular Material?

Currently immersed in the Angular 15 migration process... Today, I encountered an issue with a password input that displays two eyes the first time something is entered in the field. The HTML code for this is as follows: <mat-form-field appearance=&qu ...

The error TS2304 occurs when the e2e tsconfig types cannot find the name 'browser'

I am facing challenges while setting up a sample angular project with a basic webdriverio end-to-end test, encountering some compilation errors in my e2e test. Setting up tsconfig The project is configured with the following key files: e2e / test / [e2e t ...

Angular: the xhrRequest is failing to be sent

I am facing an issue with a service function that handles an HTTP post request. The request does not get sent for some reason. However, when I add a subscription to the post method, the request is successfully executed. In other services that have the sam ...

Leverage React components for efficient code reuse and export modules for

I have Project X, which was built using the command "yarn build" and it generated a main.js file. I am trying to use this main.js file as a dependency for another React project Y. Unfortunately, following the steps from React components and module exports ...

The FirebaseX Ionic native plugin received 2 arguments instead of the expected 3-4

Trying to implement Firebase Phone Auth with the FirebaseX plugin, I encountered an issue. Here is the code snippet I used: async getVerificationCode(): void { const res:any = await this.firebaseX.verifyPhoneNumber('+16505553434', 60); ...

Using ngFor to display images with src attribute, merging information from two different properties within the loop

One issue I am facing involves an array with properties: export interface IGameTag{ name: string; relativePath: string; filename: string; } I understand that it is possible to include the filename in the relativePath like this: <div *ngFor=" ...

Returning a value with an `any` type without proper validation.eslint@typescript-eslint/no-unsafe-return

I am currently working on a project using Vue and TypeScript, and I am encountering an issue with returning a function while attempting to validate my form. Below are the errors I am facing: Element implicitly has an 'any' type because expression ...

React is struggling to locate the specified module

Once I've set up my react project using npx create-react-app called my-app, I proceed to run npm start only to encounter the error shown in the image below. Running node version: 12.16.1 with npm version: 6.13.4 View the error message her ...