How to Avoid the Infamous "Cannot read properties of undefined" Error with TypeScript
Tags: typescript, javascript
Published: 2021-12-10 13:47:46

As a JavaScript developer, I’m sure you’ve encountered the frustrating runtime TypeError `Cannot read properties of undefined`. TypeScript gives you two ways of interpreting `null` and `undefined` types, also known as Type Check Modes, and one of them can avoid this easily overlooked TypeError.

Until TypeScript 2.0, there was only one type check mode - regular - and it considers `null` and `undefined` as subtypes of all other types. This means `null` and `undefined` values are valid values for all types.

TypeScript 2.0 introduced [Strict Type Check Mode](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html) (also referred to as strict null checking mode). Strict Type Check differs from Regular Type Check because it considers `null` and `undefined` types of their own.

I’ll show you how Regular Type Check handles `undefined` (the same applies to `null`) and how Strict Type Check prevents you from introducing unwanted behavior in our code, like that infamous TypeError `Cannot read properties of undefined`.

## When undefined becomes a problem
The function translatePowerLevel below takes a number as argument and returns strings `one`, `two`, `many` or `it's over 9000!`.

```typescript
function translatePowerLevel(powerLevel: number): string {
  if (powerLevel === 1) {
    return 'one';
  }
  if (powerLevel === 2) {
    return 'two';
  }
  if (powerLevel > 2 && powerLevel <= 9000) {
    return 'many';
  }
  if (powerLevel > 9000) {
    return 'it\'s over 9000!';
  }
}
```


However, this code doesn't handle `0`, a valid input - yes, looking at you, Yamcha.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/963s867d7qgl11c8q3qs.png"><figcaption>Yamcha's Power Level</figcaption>
</figure>
 
When JavaScript reaches the end of a function that has no explicit return, it returns `undefined`.

The `translatePowerLevel` function return value is typed explicitly as `string`, but it is possibly also returning `undefined` when the argument `powerLevel` has the value `0`. Why is TypeScript not triggering an error?

In Regular Type Check Mode, TypeScript is aware that a function might return `undefined`. But at the same time, TypeScript infers the return type to be only of type string because TypeScript is [widening](https://www.typescriptlang.org/play#example/type-widening-and-narrowing) the `undefined` type to `string` type.

As another example, if you assign `null` or `undefined` to variables while in Regular Type Check Mode, TypeScript will infer these variables to be of type `any`.

```typescript
const coffee = null; 
const tea = undefined;
```

Interpreting `undefined` or `null` as subtypes of all other types can lead to runtime problems. For example, if you try to get the length of the result of `translateNumber(0)`, which is `undefined`, JavaScript will throw this TypeError at runtime: `Cannot read properties of undefined (reading 'length')`.

```typescript
const powerLevel = translatePowerLevel(0); // undefined
console.log(powerLevel.length); // Uncaught TypeError: Cannot read properties of undefined (reading 'length')
```

Unfortunately, TypeScript’s Regular Type Check Mode is not able to alert you to when you may have made that mistake.

## Strict Type Check Mode to the Rescue
Strict Type Check Mode changes how TypeScript interprets `undefined` and `null` values. But first, let's enable Strict Type Check Mode.

### How to Enable Strict Type Check Mode in TypeScript
In the root of your project, there should be a `tsconfig.json` file. This is the TypeScript’s configuration file and you can read more about it [here](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).

```javascript
// tsconfig.json example
{
  "compilerOptions": {
    "module": "system",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "outFile": "../../built/local/tsc.js",
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.spec.ts"]
}
```
Inside `compilerOptions` property, all we need to do is add the property `"strictNullChecks": true`.

It will look something like this:

```javascript
// tsconfig.json
{
  "compilerOptions": {
    "module": "system",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "outFile": "../../built/local/tsc.js",
    "sourceMap": true,
    "strictNullChecks": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.spec.ts"]
}
```

Now that we have switched to Strict Type Check Mode, TypeScript throws this error for `translatePowerLevel` function: `Function lacks ending return statement and return type does not include 'undefined'`.

That error message is telling you the function is returning `undefined` implicitly, but its return type does not include `undefined` in it.

Awesome! TypeScript is now aware the return type does not match all possible return values, and this could lead to problems at runtime! But how can you match the return type to all possible return values?

You can either add a return statement so the function always returns a `string` (solution #1), or change the return type from string to `string | undefined` (solution #2).

### Match All Possible Return Values: Solution #1
Adding a return statement so it is always explicitly returning a value - in the code below, it is now returning the string `zero`.

```typescript
// Solution #1: add a return statement so it always returns a string
function translatePowerLevel(powerLevel: number): string {
  if (powerLevel === 1) {
    return 'one';
 }
 if (powerLevel === 2) {
    return 'two';
  }
  if (powerLevel > 2 && powerLevel <= 9000) {
    return 'many';
  }
  if (powerLevel > 9000) {
    return 'it\'s over 9000!';
  }
  // new return statement
  return 'zero';
}
```

### Match All Possible Return Values: Solution #2
Make the `undefined` return type explicit so wherever `translatePowerLevel` is used, you have to handle `nullish` values as well.

```typescript
// Solution #2: return type as string | undefined
function translatePowerLevel(powerLevel: number): string | undefined {
  if (powerLevel === 1) {
    return 'one';
  }
  if (powerLevel === 2) {
    return 'two';
  }
  if (powerLevel > 2 && powerLevel <= 9000) {
    return 'many';
  }
  if (powerLevel > 9000) {
    return 'it\'s over 9000!';
  }
}
```
If you were to compile the following code again using Solution #2, TypeScript would throw the error `Object is possibly 'undefined'`.

```typescript
const powerLevel = translatePowerLevel(0); // undefined
console.log(powerLevel.length); // Object is possibly 'undefined'.
```
When you choose a solution like Solution #2, TypeScript expects you to write code that handles possible `nullish` values.

## There’s no reason not to use Strict Type Check Mode
Now you understand how TypeScript interprets `null` and `undefined` types and how you can migrate your project to Strict Mode.

If you are starting a new project, you should definitely enable Strict Type Check Mode from the beginning. And in case you will migrate from Regular to Strict Type Check, our team can help with strategies to do so in a less painful way.

At Bitovi we highly recommend using - or migrating to - Strict Type Check Mode, as it can help you produce better, more reliable code. If you need help with building amazing web apps feel free to reach us at [bitovi.com](https://www.bitovi.com/web-application-consulting-services).

> Originally posted in [Bitovi Blog](https://www.bitovi.com/blog/how-to-avoid-the-infamous-cannot-read-properties-of-undefined-with-typescript)