# Don’t let your generic functions lie to you

In our last lesson, we looked at generic components in React. In this lesson, we'll explore a common pitfall with generic functions: they can lie to you.

## TypeScript Lets You Lie To It

TypeScript has a feature that's not present in many other languages. It lets you break out of its type system.

You can do this in many different ways. The most common is to use `as`:

```ts twoslash
type User = {
  id: number;
  name: string;
};

const fakeUser = {} as User;

// No error, but errors at runtime
fakeUser.name.toUpperCase();
```

Here, we're telling TypeScript that `fakeUser` is a `User`. But it's not. It's an empty object.

This means that we can call methods that don't exist at runtime, like `toUpperCase`. TypeScript won't complain, but your code will crash.

These are called assertions. My [book chapter](https://www.totaltypescript.com/books/total-typescript-essentials/annotations-and-assertions#assertions-forcing-the-type-of-values) covers them all.

## Generic Functions Can Hide Assertions

Assertions are usually easy to spot. `as`, `any`, `!` and `@ts-ignore` are all red flags that an unsafe operation is happening.

But generic functions can hide assertions from you.

Let's look at an extremely common piece of code: `document.querySelector`.

```ts twoslash
const element = document.querySelector(".player");
//    ^?
```

Here, we're retrieving an element from the DOM with a class of `player`. But TypeScript doesn't know what type of element it is. It could be a `div`, a `span`, or anything else. So, it uses the widest type that describes all the possibilities: `Element`.

But what if we pass a type argument to `querySelector`?

```ts twoslash
const element =
  document.querySelector<HTMLAudioElement>(".player");

console.log(element);
//          ^?
```

Now, `element` is typed as a `HTMLAudioElement`. This should make you scratch your head. How does TypeScript know that `".player"` is an `audio` element?

It doesn't. This is a hidden assertion. It's the equivalent of writing:

```ts twoslash
const element = document.querySelector(
  ".player",
) as HTMLAudioElement | null;

console.log(element);
//          ^?
```

These hidden assertions are really nasty - they don't look like assertions, even to experienced developers. But they can quickly lead to runtime errors.

## Not All Global Functions Hide Assertions

The fact that this is present in a globally available function like `document` makes me tear my hair out.

Not all global functions do this. For example, `JSON.parse` doesn't allow you to pass a type argument.

```ts twoslash
// @errors: 2558
const parsed = JSON.parse<boolean>("true");
```

Here, we're attempting to pass one but getting an error. To force the type of `parsed`, we'd need to use an assertion:

```ts twoslash
const parsed = JSON.parse("true") as boolean;
```

This is a good thing. It's a reminder that we're breaking out of TypeScript's type system. It makes the assertion explicit.

(technically, `JSON.parse` returns `any` so we could use a simple variable annotation. But with [`ts-reset`](https://www.totaltypescript.com/ts-reset), it returns `unknown`. I'll assume you have it installed.)

## Is My Function Lying To Me?

It's relatively simple to make a function that lies to you. A classic example is a wrapper over `fetch`:

```ts twoslash
const fetchJson = <T>(url: string) =>
  fetch(url).then((response) => response.json() as T);
```

We've created a `fetchJson` function that fetches a URL and parses the response as JSON. We allow users to pass in a type argument of `T`, which is then used to type the returned value of `response.json`.

This means we can use it like this:

```ts twoslash
const fetchJson = <T>(url: string) =>
  fetch(url).then((response) => response.json() as T);

// ---cut---
const todo = await fetchJson<{ id: number; title: string }>(
  "/todos/1",
);

console.log(todo);
//          ^?
```

You should, by now, be able to spot the lie. There are no runtime guarantees that `todo` will have an `id` or `title` property. It could be anything. The `as` assertion is hidden inside the function.

To get rid of the lie, remove the type parameter and the assertion:

```ts twoslash
const fetchJson = (url: string): Promise<unknown> =>
  fetch(url).then((response) => response.json());
```

Users of your function will now need to assert the type themselves:

```ts twoslash
const fetchJson = (url: string): Promise<unknown> =>
  fetch(url).then((response) => response.json());

// ---cut---
const todo = (await fetchJson("/todos/1")) as {
  id: number;
  title: string;
};

console.log(todo);
//          ^?
```

This makes the assertion explicit. It's clear that we're breaking out of TypeScript's type system.

## White Lies

Let me do a mid-email turnaround. These hidden assertions are sometimes the best approach. Our `fetchJson` function is a good example.

If we're fetching data from a trusted API, we can be confident that the response will have the shape we expect.

```ts twoslash
const fetchJson = <T>(url: string) =>
  fetch(url).then((response) => response.json() as T);

// ---cut---
const todo = await fetchJson<{ id: number; title: string }>(
  "/todos/1",
);
```

We're sure that `/todos/{id}` will return a `Todo` object. But TypeScript can't know that. Perhaps the endpoint is written in a different language, or the API is poorly documented.

In these cases, forcing users of the function to make the `as` assertion explicit might feel too punishing. Assertions feel like you're having to work around TypeScript. Hiding the assertion inside the function can make the API feel more official.

In other words, white lies to TypeScript are sometimes okay. White lies you want to make official can be hidden inside a generic function.

## Summary

- TypeScript lets you lie to it with assertions.
- Generic functions, like `document.querySelector`, can hide assertions from you.
- Keeping your function non-generic often can force users of your function to assert explicitly.
- Explicit assertions are easier to spot than hidden ones.
- Hiding assertions inside generic functions make those assertions feel 'blessed' - use them carefully.

In our next and final lesson, we'll be looking at how generic functions can combine with type transformations. It's this combination that makes TypeScript's type system so powerful, and enables so many popular libraries.

Want to get the next lesson now? Click the link below:
