Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

Type-safe Data Fetching with unknown in TypeScript

August 18, 2020
typescript

This post will cover the unknown type and how it can be used for type-safe data fetching.

Unknown

An any type refresher

The any type could be used when we are unsure of the type of value. The problem with any is that no type checks will be carried out on values of type any.

What if there was a type like any that can be used for values we don’t know but was also type-safe? This is what the unknown type is!

Understanding the unknown type

Consider the function below:

function add(a: unknown, b: unknown) {
  return a + b; // 💥 Object is of type 'unknown'
}

The code contains a function that adds the parameter values together and returns the result. The parameters are of type unknown.

Type errors occur where a and b are referenced because variables of type unknown can’t be added together.

So, type checking does occur on the unknown type, unlike the any type.

If we update the function to have the implementation below, the type errors disappear:

function add(a: unknown, b: unknown) {
  if (    typeof a === "number" &&    typeof b === "number"  ) {    return a + b;
  }  return 0;}

The if statement on line 2 is called a type guard. The type guard allows TypeScript to adjust the types of a and b from unknown to number. Code within the if statement can then operate on a and b.

You can’t operate directly on variables of type unknown. We have to give TypeScript information to narrow the type so that it can be used.

Type-safe data fetching

Now that we understand the unknown type, let’s use it to fetch data in a strongly-typed manner. Consider the code below:

async function getData(
  path: string
): Promise<unknown> {
  const response = await fetch(path);
  return await response.json();
}

This is a utility function that calls a web API and returns the data in the response body. The data that is returned from the function is given the unknown type.

Let’s consume this utility function to get a person from a web API:

type Person = {
  id: string;
  name: string;
};

async function getPerson(
  id: string
): Promise<Person | null> {
  const person = await getData("/people/1");
  return person; // 💥 Type 'unknown' is not assignable to type 'Person | null'.}

A type error occurs on the highlighted line, where the person variable is returned from the getPerson function. This is because person is of type unknown, which isn’t compatible with Person or null.

We need to check that person is in fact of type Person to resolve the type error. We can use what’s called a type predicate to do this:

function isPerson(
  person: any
): person is Person {
  return "id" in person && "name" in person;
}

Notice the return type, person is Person. This is a type predicate; it is a special return type that the Typescript compiler uses to know what type a particular value is.

Let’s use the isPerson type to narrow person from unknown to Person:

async function getPerson(
  id: string
): Promise<Person | null> {
  const person = await getData("/people/1");
  if (person && isPerson(person)) {    return person; // type is narrowed to `Person`
  }
  return null;
}

The type error disappears as we expect.

Neat!

Summary

The unknown type allows us to reduce our use of any and create more strongly-typed code. We write a little more code when using unknown, but the confidence we get from knowing our code is type-safe is well worth it.

Did you find this post useful?

Let me know by sharing it on Twitter.
Click here to share this post on Twitter

If you to learn more about TypeScript, you may find my free TypeScript course useful:

Take a look