DEV Community

Cover image for How to make TypeScript understand Array.filter
Juhana Jauhiainen
Juhana Jauhiainen

Posted on • Originally published at juhanajauhiainen.com

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;
}
Enter fullscreen mode Exit fullscreen mode

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()
    }
]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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

Further reading

Narrowing
Using type predicates

Photo by Steve Johnson on Unsplash

Discussion (2)

Collapse
lukeshiru profile image
LUKESHIRU

Little extra tip, you can do that inline as well if you need to:

content.filter((content): content is Post => "title" in content).map(({ title }) => title);
Enter fullscreen mode Exit fullscreen mode

Cheers!

Collapse
tanth1993 profile image
tanth1993

I usually use interfaces for array item. Therefore, it always hints me