loading...
Cover image for 5 Typescript utility types, that will make your life easier

5 Typescript utility types, that will make your life easier

glebirovich profile image Gleb Irovich ・3 min read

Typescript 101 (5 Part Series)

1) 5 TypeScript features that will improve your codebase 2) 4 Ideas of how to harness the power of Typescript generic function 3) 5 Typescript utility types, that will make your life easier 4) Typescript Data Structures: Stack and Queue 5) Typescript Data Structures: Linked List

Luckily for us, developers, Typescript is shipped with a lot of handy utility types. They are meant to improve the readability of the code and reduce the boilerplate while working with types. In today's episode of Typescript 101, I would like to talk about 5 utility types, which I find especially useful in everyday development.

Table of content

1. Omit

Omit<T, K> according to the official documentation Constructs a type by picking all properties from T and then removing K. In other words Omit is a generic utility type, that drops keys of T specified in K. One of the use-cases where you might need this utility is working with DTOs. If your project is using a strict serialisation, you might find yourself creating a lot of boilerplate code to describe different DTOs. Let's consider an example of how we can benefit from Omit in that case:

interface Post {
  id: number;
  title: string;
  createdAt: string;
}

type CreatePostDto = Omit<Post, "id" | "createdAt">;

const createDto: CreatePostDto = {
  title: "My post",
  id: 1, // error
  createdAt: "2020-06-06" // error
};

Properties like id or createdAt are usually set by the backend and you don't have them available while creating a new entity via the API. You can simply describe this situation by omitting those keys from the Post interface.

2. Pick

Pick does the opposite of Omit. Pick<T, K> Constructs a type by picking the set of properties K from T. Continuing the same example with DTOs, here is how you can define a type of UpdatePostDto :

type UpdatePostDto = Pick<Post, "id" | "title">;

const updateDto: UpdatePostDto = {
  id: 1,
  title: "My new post",
  createdAt: "2020-06-06" // error
};

Pick and Omit can be used to achieve the same goal because Pick<Post, "id" | "title"> is the same as Omit<Post, "createdAt">. You can always decide what is shorter or more readable to use.

3. Partial

Partial<T> is a generic utility type, that makes all properties of the provided interface optional. My favourite example of using Partial is updating objects via merging. It's especially common when you are working with state management and state updates.

interface AppState {
  posts: Post[];
  userName: string;
}

function updateState(state: AppState, update: Partial<AppState>): AppState {
  return { ...state, ...update };
}

const initialState: AppState = {
  posts: [],
  userName: "Gleb"
};

const update: Partial<AppState> = {
  userName: "John"
};

updateState(initialState, update);

Partial sets all properties of AppState to optional and therefore allows you to define only updated keys, without losing type safety.

4. Readonly

Readonly<T> is another handy utility, which helps while working with immutable data. If you'd like to enforce immutability try using Readonly:

const state: Readonly<AppState> = {
  posts: [],
  userName: "Gleb"
};

state.posts = []; // error: Cannot assign to 'posts' because it is a read-only property.
const updatedState: Readonly<AppState> = { ...state, posts: [] }; // ok

5. Record

I have already talked about Record<T, K> in this post, but this utility is definitely worth mentioning one more time.
During my daily duties, I have to deal a lot with data grids. Most of them have a very similar pattern: they define every row as a key-value map. It's often the case that the row interface can be defined quite loosely:

type Cell = string;

interface Row {
  [key: string]: Cell;
}

It means that you can potentially add as many keys as you want. Here is an example of the row that represents a single post object:

const post: Post = { id: 1, title: "My post", createdAt: "2020-06-06" };

const row: Row = {
  title: post.title,
  createdAt: post.createdAt,
  randomKey: "is allowed"
};

Luckily there is a nice way to constrain allowed keys by using Record:

type PostRow = Record<keyof Post, Cell>;

const postRow: PostRow = {
  id: post.id.toString(),
  title: post.title,
  createdAt: post.createdAt,
  randomKey: "is not allowed" // error
};

This approach makes the expected type of the row transparent to the developers and keeps it type-safe. Also with just a little effort, you can create a reusable generic row type:

type PostRow<T> = Record<keyof T, Cell>;

const postRow: PostRow<Post> = {
  id: post.id.toString(),
  title: post.title,
  createdAt: post.createdAt,
  randomKey: "is not allowed" // error
}; 

Summary

Today we discovered some superpowers of Typescript utility types and I hope you have enjoyed exploring them with me! I am very interested in your feedback. If you want to learn something specific about Typescript or webdev in general, leave a comment and let's discuss it together.

If you liked my post, please spread a word and follow me on Twitter 🚀 for more exciting content about web development.

Typescript 101 (5 Part Series)

1) 5 TypeScript features that will improve your codebase 2) 4 Ideas of how to harness the power of Typescript generic function 3) 5 Typescript utility types, that will make your life easier 4) Typescript Data Structures: Stack and Queue 5) Typescript Data Structures: Linked List

Posted on by:

glebirovich profile

Gleb Irovich

@glebirovich

Tech Junkie | Pizza Lover 🍕 | Frontend Wizard 🧙‍♂️ | React Addicted 🚀

Discussion

markdown guide
 

I've used the first 3 a lot when creating prop interfaces in React. There are a lot of scenarios where you need to extend from a base interface and sometimes you require the properties of the base interface to be optional. And sometimes you need just some of the properties of the base parent. Typescript utility types make this process a lot easier.
Although, I've never used Record. I've seen it in Mui but not used it in my own code. Seems handy, thanks!

 

React with typescript is awesome, especially once you manage to properly type HOCs 😂