DEV Community

Asjad Anis
Asjad Anis

Posted on

TypeScript: Custom Type Guards

Did TypeScript ever annoy you with the error Object is possibly undefined even if you filter out all the undefined values, if yes then this post is for you.

Code Setup

Let's quickly setup a very basic example to see how we can get to such a situation.

interface Item {
  id: string;
  name: string;
  description: string;
}

const myItems: Array<Item> = [
  { id: "1", name: "item-1", description: "bla bla bla" },
  { id: "2", name: "item-2", description: "yada yada yada" },
  { id: "3", name: "item-3", description: "bla bla bla" },
  { id: "4", name: "item-4", description: "yada yada yada" }
];

const itemIds: Array<string> = ["4", "5", "2", "6"];

const getItems = (id: string): Item | undefined => {
  return myItems.find((item) => item.id === id);
};

const items = itemIds.map(getItems);

console.log(items);
Enter fullscreen mode Exit fullscreen mode

In the above example we have an array of Items and list of itemIds , and we are trying to get the metadata of the itemId . When you run this code and hover over items , TypeScript will tell you that the variable items is of type (Item | undefined)[] which makes sense as some id's won't match and return undefined so far so good, now the undefined items in the array are of no use to us and we don't want to deal with the undefined errors when further referring the items variable in our codebase, so lets quickly fix this by filtering out all the undefined values.

const items = itemIds.map(getItems).filter(item => !!item);
console.log(items) // No undefined values in items
Enter fullscreen mode Exit fullscreen mode

Great now we can easily loop over the items array and process it without having to deal with undefined values, let's quickly try it.

const processItem = (item: Item) => {
  return {
    ...item,
    name: item.name.toUpperCase()
  }
}

const processedItems = items.map(item => {
   return processItem(item) // Argument of type 'Item | undefined' is not assignable to parameter of type 'Item'
})
Enter fullscreen mode Exit fullscreen mode

Wait but why? we already handled this above, let's hover over items and see it's type

const items: (Item | undefined)[] // Wait whaaat
Enter fullscreen mode Exit fullscreen mode

The problem is that the .filter method will always return an array of the type with which you initially started so in our case it was Item | undefined which makes sense now but how do we fix this 🤔

Type Guards to the Rescue

This is where TypeScript custom type guards come to rescue, a custom type guard is basically a function that's return type is a type predicate. It's a very simple and intuitive approach for fixing the above problem properly, let's see it in action.

// Our type-guard
const isItem = (item: Item | undefined): item is Item => {
  return typeof item !== "undefined"
}

const items = itemIds.map(getItems).filter(isItem); //items: Item[]
Enter fullscreen mode Exit fullscreen mode

And that's how you can filter out the undefined values properly in TypeScript with type guards and keeping the types intact in your codebase. This same approach can be extended to a use-case where you are dealing with a Union type and you want to make sure you are accessing the right properties of the instance of that type.

As always do share your thoughts on this approach in the comments below and feel free to connect with me on Twitter.

Discussion (0)