blog.larah.me

Write explicit type guards

August 25, 2020

Inside a function with arguments, you may want to write a guard to check that a parameter has been set before using it.

Example:

function myMethod(foo: ?string) {
  if (!foo) {
    return;
  }

  console.log(`The value of the foo string is: ${foo}`);
}

Great! We checked the argument before trying to use it, making Flow/TypeScript happy and adding resilience to our code.

In some cases, this is all you need - and there’s nothing wrong with this!

So when is this not ok? Read on…

The dangers of type coercion

This approach of if (foo) above relies on type coercion from a stringy value to a boolean value.

But what if our input is the empty string? Empty strings are falsey, so our function wouldn’t print anything - maybe not what we wanted! This problem also affects numbers, boolean arguments, or anything else that could coerce to falsey.

Let’s say we do want to allow for empty strings - we’ll need to try something else. Maybe we could use === and check explicitly for null?

function myMethod(foo: ?string) {
  if (foo === null) {
    return;
  }

  console.log(`The value of the foo string is: ${foo}`);
}

type !== undefined

What if foo was undefined? That would pass the above guard and break our code, since undefined !== null! So we also need to check for that:

function myMethod(foo: ?string) {
  if (foo === null || foo === undefined) {
    return;
  }

  console.log(`The value of the foo string is: ${foo}`);
}

Perfect - our guard now behaves as expected!

Bonus: Check for types explicitly

if (foo === null || foo === undefined) is a bit tedious to write out each time.

Rather than disallowing a known set of bad types (undefined, null), we can do even better and only allow the type that you want:

function myMethod(foo: ?string) {
  if (typeof foo !== 'string') {
    return;
  }

  console.log(`The value of the foo string is: ${foo}`);
}

This avoids any type coercion issues, and provides an extra runtime check that your inputs are the type you expect.

There’s not a huge amount of downside here, and explicitness is never usually a bad thing… If in doubt, go for this option.

Takeaway

  • remember that undefined !== null. 90% of the time you’ll want to guard against both values.
  • even better is to explicitly check for the type you do want, vs checking for types you don’t want.

Reference

Here’s a list of examples for how to check for various types:

Type Check
Number typeof myVar === 'number'
String typeof myVar === 'string'
Array Array.isArray(myVar)
Date myVar instanceof Date
Object typeof myVar === 'object'

Further Reading

Discuss on TwitterEdit on GitHub