Background image

How to make TypeScript understand Array.filter

If you've ever used Array.filter to filter a list to certain type of items, you've probably been hit by TypeScript not realizing what type of items your list contains after filtering. To fix this, you can use user-defined type guards.

Let's say we have content items of two type: Post and Image.

interface Post {
text: string;
title: string;
date: Date;
}
interface Image {
url: string;
alt: string;
date: Date;
}

We are going to store items of both types in a array.

type Content = Post | Image;
const content: Content[] = [
{
title: "A post",
text: "...",
date: new Date()
},
{
alt: "A image",
url: "...",
date: new Date()
}
]

Now if we want to get a list of all the post titles, we can do it by first filtering filtering with "title" in obj. But even though we know this works and there's no way title is undefined, we still get a type error.

const postTitles = content.filter(c => "title" in c).map(c => c.title);
// Property 'title' does not exist on type 'Content'.
// Property 'title' does not exist on type 'Image'.(2339)

This is because TypeScript can't deduce that all the remaining items are of the type Post. We can fix this issue with a TypeScript feature called user-defined type guards

What are user defined type guards?

Type guards allow narrowing a type with differenty ways. For example, you can use typeof or instanceof.

User-defined type guards are functions whose return type is a type predicate.

function isPost(content: Content): content is Post {
return "title" in content;
}

Notice the return value of the function is content is Post this tells TypeScript the function will only return true if content is of type Post. The first part of a type predicate (here content) must be a name of parameter of the function.

Now we can use this type guard in our Array.filter and TypeScript won't yell at us anymore 🥳

const postTitles = content.filter(isPost).map(c => c.title);

If you want to play with the code in this article, checkout this playground

Further reading

Narrowing
Using type predicates

You might also enjoy