paint-brush
Working With Mapped Types in TypeScriptby@javidasgarov
1,516 reads
1,516 reads

Working With Mapped Types in TypeScript

by Javid AsgarovMay 9th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

The main aim is to grasp the workings of mapped types in [TypeScript] and explore various typical scenarios. In this guide, I'll explain how mapped types can be utilized and the assistance provided by utility types. Let’s say, that you have an object, that describes some entity, like a person. And you want to have a [hashmap], that stores what component to render for each field. For that, we might use something called a mapped type.
featured image - Working With Mapped Types in TypeScript
Javid Asgarov HackerNoon profile picture


The main aim of this piece is to grasp the workings of mapped types in TypeScript and explore various typical scenarios. I frequently come across mapped types in my work. In this guide, I'll explain how mapped types can be utilized and the assistance provided by utility types.


Let’s say, that you have an object, that describes some entity, like a person.


interface Person {
	name: string;
	secondName: string;
	age: number;
    description: string;
	email: string;
    id: number;
}


And you want to have a hashmap, that stores what component to render for each field when rendering Person on a page. For that, we might use something called a mapped type.



type PersonInputType = {
  [key in keyof Person]: () => React.JSX.Element;
}


Here, keyof returns the union type of all the keys in Person interface.

What that means is, that type PersonInputType looks like this:


type PersonInputType = 'name' | 'secondName' | 'age' | 'description' | 'email';


Now, suppose that you don’t have components mapped out for each property from Person. For example, there is probably no component should be rendered for the id key.

One thing you can do is, add an optional parameter.


type PersonInputType = {
  [key in keyof Person]?: React.Component;
}


But what if you want everything to be mapped out except for ID. One way to go about that is to use Pick from Typescript to list all the keys from the object we need.


type PersonInputType = {
  [key in keyof Pick<Person, 'name' | 'secondName'>]: () => React.JSX.Element;
}


But it would be annoying to list all the needed keys just so one element would be excluded. For that, we can use Omit.


type PersonInputType = {
  [key in keyof Omit<Person, 'id'>]: () => React.JSX.Element;
}


Now, that looks better. Let’s continue with utility types. I’ve demonstrated that you can make properties from the original type by using ‘?’ at the end. Also, you can use a Partial utility type.

For example:


type PersonInputType = {
  [key in keyof Partial<Person>]: React.Component;
}


Partial makes every key from that object optional. Now what if, the situation was reversed? Some of the Person properties would be optional, but in the mapped type, you need all types to be required and defined. Then we can use Required.


type PersonInputType = {
  [key in keyof Required<Person>]: React.Component;
}


Let’s also consider typeof. As the name suggests typeof returns the type of an object. When it might be useful to use both keyof and typeof? For example, you have an enum, describing app states.


enum AppStates {
  'READY' = 'READY',
  'LOADING' = 'LOADING',
  'ERROR' = 'ERROR'
}


And now you need a hashmap to specify text messages for each state. How to use these keys from the enum? You can do the following


type AppStatesInfo = {
  [key in keyof typeof AppStates]: string;
}



Here, typeof here returns the typeof an object, and keyof we already discussed and now we can combine both. You can also avoid using keyof and typeof in this case and just write it shorter:


type AppStatesInfo = {
  [key in AppStates]: string;
}


Does the same thing. The one problem here might be, that enums a lot of times are capitalized, no specific reason, just sort of a convention. But keys in a hashmap aren’t. We can again come back to utility types, and make keys lowercase.


type AppStatesInfo = {
  [key in Lowercase<AppStates>]: string;
}



I think this might cover a lot of everyday cases. Mapped types help you derive types from interfaces, objects, and enum you have in your application to connect one type to others.