words.
Navigate back to the homepage

🕹 TypeScript Cheats: Property 'id' does not exist on type '{}'.

May 17th, 2020 · 1 min read
Photo by Daniel Jensen on Unsplash

TL;DR:

Either:

1const isValidObject = (myObject as ValidObject).id !== undefined;

Or, better, define a type guard:

1function isValidObject(myObject: ValidObject | {}): myObject is ValidObject {
2 return (myObject as ValidObject).id !== undefined;
3}

I’m publishing this tip mainly because it’s the third time I run into this problem, and the third time I get lost on the internet trying to understand what it is I’m doing wrong. I hope next time I search for this, this post will come up! Read on if you want a better understandind of what the cheat-sheet code above does and where it comes from:

When writing regular JavaScript we are used to a certain degree of flexibility when it comes to objects. Take the following example:

1// Imaginary call to an API that returns the venue with ID 1,
2// or an empty object if there is no venue with that ID
3const venue = getVenue(1);
4
5// Let's check if a venue was found by verifying the existence of the `id` property
6const weHaveVenue = venue.id !== undefined;
7
8if (weHaveVenue) {
9 // do something
10} else {
11 // do something else...
12}

Pretty straightforward, right?

Well, the moment we use TypeScript, things don’t work so smoothly anymore. Have a look a this implementation:

1// Let's define the type of our imaginary API function first
2type GetVenue = (
3 id: number
4) => { id: number; name: string; location: string } | {};
5
6// And then write a sample (and NOT real world production code) implementation
7// faking an API call that might or might not find (and return) a venue
8const getVenue: GetVenue = function(id) {
9 const state = id < 10 ? 200 : 404;
10
11 return state === 200
12 ? {
13 id,
14 name: "Meetings Central",
15 location: "North Pole",
16 }
17 : {};
18};
19
20const venue = getVenue(1);
21
22const weHaveVenue = venue.id !== undefined; // ❌ Property 'id' does not exist on type '{}'.
23
24if (weHaveVenue) {
25 // do something
26} else {
27 // do something else...
28}

I know what you are thinking: “Wait, I know that. That’s exactly why I’m checking on the id!“. But TypeScript needs a little more holding hands:

1// Let's define two more types since we will have to reuse them in our code
2type Venue = { id: number; name: string; location: string };
3type NoVenue = {};
4
5type GetVenue = (id: number) => Venue | NoVenue;
6
7const getVenue: GetVenue = function(id) {
8 const state = id < 10 ? 200 : 404;
9
10 return state === 200
11 ? {
12 id,
13 name: "Meetings Central",
14 location: "North Pole",
15 }
16 : {};
17};
18
19const venue = getVenue(1);
20
21// By casting our `venue` to a `Venue` type, and then checking on the `id` property,
22// we are basically telling TypeScript: "trust us, at runtime we're gonna be fine"
23const weHaveVenue = (venue as Venue).id !== undefined; // ✅
24
25if (weHaveVenue) {
26 // do something
27} else {
28 // do something else...
29}

Hurrah 🙌

This may (and will) work well in several, simple cases. But what if further down we also want to use that venue object? Let’s say we need an upper-cased version of the venue name, and add one line of code to our if/else statement:

1[...]
2
3if (weHaveVenue) {
4 // do something with our venue object
5 const upperName = venue.name.toUpperCase(); // ❌ Property 'name' does not exist on type 'NoVenue'.
6} else {
7 // do something else...
8}

Whoops 😕. Back at square one.

In this case we need to move our check in a custom type guard, which is fancy wording “a function that checks a type”. Check out the full code:

1type Venue = { id: number; name: string; location: string };
2type NoVenue = {};
3type GetVenue = (id: number) => Venue | NoVenue;
4
5// We move our id check into a function whose return type is "value is Type"
6function isVenue(venue: Venue | NoVenue): venue is Venue {
7 return (venue as Venue).id !== undefined;
8}
9
10const getVenue: GetVenue = function(id) {
11 const state = id < 10 ? 200 : 404;
12
13 return state === 200
14 ? {
15 id,
16 name: "Meetings Central",
17 location: "North Pole",
18 }
19 : {};
20};
21
22const venue = getVenue(1);
23
24// We can now call our type guard to be sure we are dealing with one type, and not the other
25if (isVenue(venue)) {
26 // do something with our venue object
27 const upperName = venue.name.toUpperCase(); // ✅
28} else {
29 // do something else...
30}

To paraphrase the official TypeScript documentation:

Any time isVenue is called with some variable, TypeScript will narrow that variable to that specific type if the original type is compatible.


This brief excursion should’ve clarified a feature of TypeScript that may leave someone coming from JavaScript perplexed. At least, it troubled me a few times! I’d love to hear your comments: let’s be friends on Twitter (@mjsarfatti, DMs are open) and on dev.to.

If you’d like to be notified of the next article, please do subscribe to my email list. No spam ever, cancel anytime, and never more than one email per week (actually probably much fewer).

Join my email list and get notified about new content

At most once a week, most probably once a month. Also: unsubscribe at any time.

More articles from words.

💊 Pills of WebGL: An Introduction

The first in a series of articles that will explore the magical world of drawing in a browser, illustrated and in plain english.

January 30th, 2020 · 10 min read

🤖 I Was Bored, so I made a fun little Twitter bot

I thought to myself, wouldn’t it be nice if there was a resource that collected the fun-nest and most interesting tutorials out there? And so I made a Twitter bot

May 25th, 2020 · 3 min read
© 2020 words.
Link to $https://instagram.com/mjsarfattiLink to $https://twitter.com/mjsarfattiLink to $https://dev.to/mjsarfattiLink to $https://github.com/mjsarfatti