Infer in TypeScript, the Great and Powerful
Tags: typescript, webdev, programming, tutorial
Published: 2021-11-22 04:46:17

Let's talk about the `infer` keyword in TypeScript. I believe, there're a lot of developers, who've been confused about `infer`. When should I use it? How it works? What is the purpose?

![Confused developer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pfnn335fcnpvohqi4ape.gif)

Let's start with something easier. The next cases will help us to understand the concept of `infer`. Ok, look at the piece of code:

```ts
let a = 10;
```

TypeScript knows a type of `a` — it's a number. This fact allows developers to not write such types everywhere. TypeScript takes care of it. By the way, where will be another type in case of `const` usage. Try it by yourself. I'm waiting for your explanation in the comments. This is a small interactive with a reader)

Ok, we've just found out, TypeScript is pretty smart. If a value corresponds to a specific condition, TS can make a decision about its type. Something like this:

```ts
if (typeof a === 'number') {
  return number;
} else {
  return ???;
}
```

What does `???` mean?) Meet the `never` type. Looks quite useless, who'd like to use it? In TypeScript, `never` is treated as the no value type. Do we have any functions, which do not return a value (undefined is a correct value)? By the way, we can create such function. For example:

```ts
function foo() {
  throw new Error('error');
}
```

Check out the return type of `foo`.

Actually, the `never` type is not so useless. Let's come back to that example with number. We can use `never` in the else condition:

```ts
if (typeof a === 'number') {
  return number;
} else {
  return never;
}

// or

typeof a === 'number' ? number : never;
```

Of course, where is no such code in TypeScript's codebase. It's just a model, how TypeScript works. 

And there is an equivalent for if/else in TypeScript. I'm talking about the `extends` keyword:

```ts
type NumberFromType<T> = T extends number 
  ? number 
  : never;
```

Hm, there is something new — `<T>`. Let's think about it like it is a box for a type. `T` can be any type. We will put the type into that box as soon as it will be defined anywhere in your statement. The classic example:

```ts
function test<T>(x: T): T {
  return x;
}

test(10);
```
If you call it like `test(10);`, TypeScript will defined `T` as `number`, cause `x` was `number`. This is quite simple explanation, I know. But it's enough right now) Ok, we can moving on.

Let's come back to `NumberFromType`. `T extends number` means that it’s safe to assume that a value of type `T` is also of type `number`. For example, 10 extends `number` because `let a: number = 10` is type-safe. Hm, what happens, if we pass a string into `NumberFromType<T>`? :

```ts
type A = NumberFromType<'10'>;

// It will be treated like this:

type NumberFromType<'10'> = '10' extends number ? number : never; 
```
As a result, there will be `never` in the type `A`, cause string is not extended from number. But what if we need to work with strings too? We can put one condition into another. Something like this:

```ts
type StringOrNumberFromType<T> = T extends string
  ? string
  : T extends number
    ? number
    : never
```
And voila, it works for strings and numbers) So, you can go deeper and put more conditions there, for each of type.

## The infer keyword

Finally we are ready for `infer`! First of, the `infer` keyword can be used in conditional types only. And there is a really simple explanation — you can not use it anywhere else =) This keyword doesn't have any meanings outside of a conditional type. 

Ok, let's try to pass an array of numbers into `NumberFromType`. The result is `never`, obviously. An array of numbers is not extended from number. But what if we need to get a type of an array item? It can be something like this:

```ts
type ArrayItemType<T> = T extends ...
```

What should go next in that statement? We need to check, that `T` is an array:

```ts
type ArrayItemType<T> = T extends [] ? ... : never;
```

I've just finished the condition, like it was in `NumberFromType`. But the main question is not resolved, cause we need a type of the array's item. By the way, we can write a type for array of numbers like `number[]` or `Array<number>`. Any other type can be inside `<>` braces. So, our condition can be written in a such way:

```ts
type ArrayItemType<T> = T extends Array<ITEM_TYPE> 
  ? ITEM_TYPE 
  : never;
```
Ok, it's much better! But `ITEM_TYPE` is not defined. It has not been inferred yet. Yes, we need TypeScript to infer the type. We can ask TypeScript to do it:

```ts
type ArrayItemType<T> = T extends Array<infer ITEM_TYPE> 
  ? ITEM_TYPE 
  : never;
```

It means, if `T` is extended from an `Array` type, so, TypeScript, it would be really kind of you, if you will infer the type of `T's` item and will return it as a result.

In general, we can say, that the `infer` keyword and conditional typing in TypeScript allows us to take a type and isolate any piece of it for a later usage. 

There are some real life examples. 

### Unpromisify

```ts
export type Unpromisify<T> = T extends Promise<infer Result> 
  ? Result 
  : never;
```

As it follows from its name, `Unpromisify<T>` returns a Promise's result. By the way, if you use TypeScript 4.5 or higher, you can use the built in type `Awaited`. There are some examples with `Awaited` on [typescriptlang.org](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#the-awaited-type-and-promise-improvements).

### ComponentProps

In React, we often need to access prop types of a component. To do that, React offers a utility type for accessing prop types powered by the `infer` keyword called `ComponentProps`. You can find the full  definition in [DefinitelyTyped repository](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L832). 
```ts
type ComponentProps<
    T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>
  > =
        T extends JSXElementConstructor<infer P>
            ? P
            : T extends keyof JSX.IntrinsicElements
                ? JSX.IntrinsicElements[T]
                : {};
```

After checking that `T` is a React component (`T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>`), it infers its props and returns them (`T extends JSXElementConstructor<infer P> ? P`). And the tail is about a simple React Element.

## Conclusion

The `infer` keyword is a powerful tool that allows us to unwrap and store types from any complex type. It is like a type unboxing. So, there is no any mystery behind this keyword.

If you want challenge your TypeScript skills, I'd like to recommend you [type-challenges](https://github.com/type-challenges/type-challenges). And I'm pretty sure, this post will be really useful. Enjoy)