Advanced TypeScript Types with Examples

Improve your understanding of TypeScript and learn these advanced techniques to help you master the language and utilize TypeScript with React

Elena Sufieva
Level Up Coding

--

Different types of fruits:) Photo by Luke Michael on Unsplash

When I began using TypeScript last winter, I have grown from defaulting to using any for all types more complex than a string or number to now feeling comfortable with the usage of advanced built-in types and custom types. Add types to your JavaScript code by switching to TypeScript to write air-tight applications. This article will provide examples of using advanced types and also how to use them in a React application.

We will discuss the Record, Partial, Required, Pick and a custom Omit types.

Record

A very useful built-in type introduced by Typescript 2.1 is Record: it allows you to created a typed map and is great for creating composite interfaces. To type a variable as Record, you have to pass a string as a key and some type for its corresponding value. The simplest case is when you have a string as a value:

const SERVICES: Record<string, string> = { 
doorToDoor: "delivery at door",
airDelivery: "flying in",
specialDelivery: "special delivery",
inStore: "in-store pickup",
};

This may appear trivial, but it provides easy typing in your everyday code. One of the popular cases when Record works in well is an interface for a business entity that you keep in a dictionary as key-value pairs. This model could represent a collection of contacts, events, user data, transportation requests, cinema tickets, and more. In the following example, we create a model for products that a user could add to her cart:

Type Record is used for a dictionary of products in a user cart.

You see how the editor autocompletion will help us to define a typed object and will mark the variable with an error because not all the required properties are defined:

Webstorm autocomplete tool suggests to add name and amount for CartState variable

Also, Typescript does not allow us to create an empty object for some defined shape and then populate it with properties, but here Record will come to the rescue.

It is also possible to use a string enum as a key in the Record type. For example, we will use ErrorsEnum to keep and access possible error values (messages):

Use Record for errors dictionary in business model

Let’s see how you can use it for type enhancing when working with Material-UI library. As the guide says, you can add your custom styles with CSS-in-JS notation and inject them via withStyles HOC. You can define styles as a function that takes a theme as an argument and returns the future className with correspondent styles and you want to define a type for this function:

Adding type for styles function in every component file

You notice that it can become very annoying to add these as CSSProperties for every style object. Alternatively, you can use the benefit of the Record type and define a type for the styles function:

Using once defined createThemeFunction type everywhere

Now you can use it safely in every component and get rid of hardcoding type of CSS properties in your styles.

Partial and Required

Partial type makes all properties in the object optional. It could help you in many cases, like when you’re working with the data that a component would render but you know that the data may not be fetched at the moment of mounting:

On the left: using Partial type to reach the same result as on the right where every property is marked optional

Or you can use Partial to define some of the props as default props for your component:

Use Partials in typings for default props

As the opposite, the Required built-in type introduced in Typescript v2.8, makes all properties of a described object required:

All fields in OwnProps of this component are required

One of the use cases for Required is selectors: sometimes you want to make a selector for a property from a nested object and you know that at the moment of selector invocation this property will be defined. You may point it out with a typing:

A small hack to ensure compiler that ticketOffer is required for the selector processing

This may look like a cheat and it can cause type errors if you start to inherit required properties from optional ones, so be careful!

Maybe it sounds stupid but it’s not a rare situation when you have code generated automatically and all interfaces that are in your hands are Partial and all elements of your UI want only Required. Here you’ll start to check every nested object on undefined 😨.

Pick and Omit

Have you ever tried to narrow a type because you realized that your next class doesn’t need this bunch of properties? Or maybe you arrived at this point in the process of refactoring, trying to distribute pieces of a system in a new way. There are several types that can solve this problem.

Pick helps you to use a defined already interface but take from the object only those keys that you need.

Omit which isn’t predefined in the Typescript lib.d.ts but is easy to define with the Pick and Exclude. It excludes the properties that you don’t want to take from an interface.

At both of these images, ProductPhotoProps will contain all Product properties except of name and description:

Pick and Omit are flexible ways to re-use your interfaces

One of a practical example of such a situation from my current project is a refactoring a large form with a complex fields dependencies. There was FormProps type where errors field was included. After re-thinking this architecture the errors became unnecessary for a first child component but still needed by the second one. I used Pick to take a portion of fields except for errors for a new interface and it worked well.

There are, of course, different ways to combine types and define their relationship. If you start to decompose a large thing into small pieces from the beginning, maybe you will solve the problem of excluding properties from an object from another side. You would instead extend types.

Extending type/interface

When you extend an interface, all properties described in a source interface/type will be available in a result interface. Let’s see how we could combine small interfaces into one that corresponds to our task:

Extending compact interfaces to reuse them for a different component typings

This method is not as handy because you have to imagine the shape of your objects in advance. On the other hand, it is fast and easy which makes it cool for prototyping or building simple UI like rendering data into read-only blocks.

Summary

We have explored some of the popular pre-defined TypeScript types with the real code examples. The project we used is only a demo but all of these types work at least in one of the real-world apps that I know 😉. I hope this article was useful for you and encouraged you to not be afraid of writing typings. I created a repo with a React-Redux SPA where you find most of these examples or their alternatives.

However, I would like to say one more thing about static typing. Often, when you explore a new technology or face a challenge during a feature development, you start to solve a technical problem and may forget about your goal. Static typing is not a goal of your work, it’s just a tool. If it becomes a central thing of a project, it’s a sign that you got carried away 🚀. Remember about balance in business/tech parts of your app and happy coding!

--

--