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

Combine two streams under certain conditions using RxJs

When working with streams, I am facing a scenario where I have two server calls to make in order to get the required response. However, if the first call returns data, I do not want to execute the second call. I have been struggling to find the proper comb ...

Transformation of Ionic 2 ScreenOrientation Plugin

Can someone assist me with this issue? A while back, my Ionic 2 app was functioning correctly using the ScreenOrientation Cordova plugin and the following code: window.addEventListener('orientationchange', ()=>{ console.info('DEVICE OR ...

Ways to access configuration settings from a config.ts file during program execution

The contents of my config.ts file are shown below: import someConfig from './someConfigModel'; const config = { token: process.env.API_TOKEN, projectId: 'sample', buildId: process.env.BUILD_ID, }; export default config as someCo ...

Unlocking the Power of Angular 12: Leveraging the Subscribe Method to Access Multiple REST APIs

We have a task where we need to make multiple REST API calls from the ngOnInit() method, one after the other. After making the first call, we need to pass the response to the second API call, and similarly for the third call, we need to get the value from ...

Imitate dependencies using Mocha and Typescript

In my typescript project, I am using mocha. Let's consider two modules: // http.ts export class Http { } // app.ts import Http from './http'; export class App { } I want to know how to mock the Http module while testing the App. The te ...

Secure a reliable result from a function utilizing switch statements in Typescript

There's a function in my code that takes an argument with three possible values and returns a corresponding value based on this argument. Essentially, it can return one of three different values. To achieve this, I've implemented a switch statem ...

Enhancing TypeScript - Managing Variables within Namespace/Scope

Why is the console.log inside the function correctly logging the object, but after the function returns it logs undefined, failing to update the variable? In addition, when using this within testNameSpace, it returns window. Why is that? namespace testNa ...

`Developing reusable TypeScript code for both Node.js and Vue.js`

I'm struggling to figure out the solution for my current setup. Here are the details: Node.js 16.1.x Vue.js 3.x TypeScript 4.2.4 This is how my directory structure looks: Root (Node.js server) shared MySharedFile.ts ui (Vue.js code) MySharedFi ...

The error code TS2345 indicates that the argument '{ read: typeof ElementRef; }' cannot be assigned to the parameter '{ read?: any; static: boolean; }'

Currently in the process of updating my Angular application from version 7 to version 8. Upon running ng serve, I encounter the following error: Error TS2739: Type '{}' is missing the following properties from type 'EmployeeModel': stat ...

Using TypeScript without specifying a specific argument in the any type

My function accesses local/session storage, but there is a condition that it can only be called if the window is defined. This function takes a generic args argument which I simplified to have a type of any[]. While using the useSessionStorage function, ...

Slow performance on Ionic page with input fields

My ionic app is experiencing slow performance on pages with inputs. For example, a select input with 4 items has a delay of approximately 800ms, and when dismissing the keyboard, a white blank block remains on screen for about 500ms. This app consists of ...

Tips for utilizing Provide/Inject in Vue.js while leveraging TypeScript

I am currently working with Vue.js and TypeScript along with the vue-property-decorator package. The documentation suggests that I can achieve something like this: import { Component, Inject, Provide, Vue } from 'vue-property-decorator' const s ...

Encountering issues while attempting to upload items to AWS S3 bucket through NodeJS, receiving an Access Denied error 403

I encountered an issue while attempting to upload objects into AWS S3 using a NodeJS program. 2020-07-24T15:04:45.744Z 91aaad14-c00a-12c4-89f6-4c59fee047a1 INFO uploading to S3 2020-07-24T15:04:47.383Z 91aaad14-c00a-12c4-89f6-4c59fee047a1 IN ...

The BullMQ library is optimizing performance by efficiently managing Redis connections

Currently, I'm in the process of implementing logic to efficiently reuse redis connections with bullMQ by referring to this specific section in the bullMQ documentation. My setup involves utilizing the latest BullMQ npm version (1.80.6). As per the ...

Error: Unable to dispatch a Redux-thunk action with additional arguments in React

Currently, I am attempting to work with TypeScript alongside Redux-Thunk and Redux. My goal is to pass an object to any action (I am utilizing dependency injection and need to pass either an object service or a string). However, I encountered the followin ...

Methods in Ionic to call an external JavaScript file from TypeScript

Having a JSON list stored in a JavaScript file, my objective is to filter it using TypeScript before sending the filtered results to the HTML homepage. However, I encountered an issue within the HTML file. It's worth mentioning that when running the ...

Change typescript so that it shows "require" instead of "import" when outputting

Currently, I am converting TypeScript code to JavaScript for an application that is specifically targeting Node v.14. My goal is to have the output contain require statements instead of import statements. This is what my configuration file looks like: { ...

I encountered an error with Firebase when attempting to run functions on my local machine

Encountering a Firebase error when running the function locally using emulator in CLI $ firebase emulators:start --only functions Initiating emulators: ["functions"] functions: Using node@8 from host. functions: Emulator started at http://localhost:50 ...

Tips for enabling TypeScript's static typings to function during runtime

function multiply(num: number): number { console.log(num * 10) // NaN return num * 10 } multiply("not-a-number") // result == NaN When attempting to call the function above with a hardcoded invalid argument type, TypeScript correctly identifies and w ...

Utilize a personalized useFetch hook in React.js to transmit a POST request and obtain a response

I recently came across a great resource on this website that provided the logic for a useFetch hook. My goal is simple - I want to send a post request and then map the response into a specific type. While this seems like it should be straightforward, I&apo ...