Jumpstart on advance typescript Part 2
Tags: typescript, advance
Published: 2020-08-08 18:05:50

---
title: Jumpstart on advance typescript Part 2
published: true
description: 
tags: typescript, advance
series: Jumpstart on advance typescript
cover_image: https://images.unsplash.com/photo-1583711565105-afd990bb44a4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1500&q=80
---

This is continuation of my previous blog where I was trying to introduce some advance Typescript concepts

# **Type Guards**

To better understand this lets look at below code block

```tsx
const numbers = [0, 1, 2, [3, 4], 5, 6, [7], [8], 9]

function flatten(array: Array<number | Array<number>>): Array<number> {
  const flatten: Array<number> = []
  for (const element of array) {
    if (Array.isArray(element)) {
      flatten.push(...element)
    } else {
      flatten.push(element)
    }
  }
  return flatten
}

flatten(numbers)
```

In this function we are just trying to flat an array (in this case, return array of numbers) which can have number or array of numbers as element. 

on line *`flatten.push(...element)`* Typescript allow us to use spread operator inside the if statement without giving any type error. Which means Typescript compiler knows the element inside the if statement will be an array of number because we put a check in if statement which will make sure it should be an array.

Which means *Array.isArray()* is a type guard in this case and protect us from type error here. Similar way we have other inbuilt type guards: *typeof* and *instanceof*

*typeof* can only be used to check primitive data types: *string, number, boolean, function, symbol, object, symbol* and *undefined* types

*instanceof* can be used to check if an object is an instance of a class. So it means we can use *instanceof* type guard on class only

But we always don't have simple data structure to deal with in real scenario so how to type guard complex object shape. The answer is create our own type guard!

### Custom Type Guard

let create a function to sum all the numbers in array

```tsx
function calculateSum(array: Array<number>) {
  return array.reduce((sum, curr) => sum + curr, 0)
}
```

 In above function we can only pass an array of numbers as argument

```tsx
const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
calculateSum(arr) // No TS error

const notFlatArr = [0, 1, 2, [3, 4], 5, 6, [7], [8], 9]
calculateSum(arr) // TS error
```

To prevent throwing error we will create our own type guard

```tsx
const notFlatArr = [0, 1, 2, [3, 4], 5, 6, [7], [8], 9]

function isFlat(array: Array<number | Array<number>>): array is Array<number> {
    return !array.some(Array.isArray);
}

if (isFlat(noFlatArr)) {
    calculateSum(arr)
}
```

Let go one step ahead. Our type guard function is very specific to numbers only. It will be great if we can use it for other types as well. So lets make it generic 

```tsx
function isFlat<T>(array: (T | T[])[]): array is T[] {
    console.log(!array.some(Array.isArray));
}
```

Now this can be used for any data type and can tell us if the array of any data type is flat or not. 

# Discriminated Union Types

Discriminated union types allow us to model a finite set of alternative object shapes in the type system. Using it we will only expose valid properties at a given location therefore we will introduce less bugs in system. Lets see some an example for better understanding

```tsx
type PersonalInfo = {
  name: string
  location: string
}

const personalInfoCollection: { [key: string]: PersonalInfo } = {
  'max@xyz.com': {
    name: 'Max payne',
    location: 'USA',
  },
  'manish@abc.com': {
    name: 'Manish Kumar',
    location: 'India',
  },
}

function getPersonalInfo(email: string) {
  const info = personalInfoCollection[email]
  if (info) {
    return {
      success: true,
      value: info,
    }
  } else {
    return {
      success: false,
      error: 'Information not found',
    }
  }
}
```

In *getPersonalInfo* function we are trying to get the info using email address, as you can see the function returns a positive case where we find the information for the email and a negative case where the information is not available for the given email. If we have to give the return type of the function what it could be?

```tsx
type Result = {
  success: boolean
  value?: PersonalInfo
  error?: string
}

function getPersonalInfo(email: string): Result {
  const info = personalInfoCollection[email]
  if (info) {
    return {
      success: true,
      value: info,
    }
  } else {
    return {
      success: false,
      error: 'Information not found',
    }
  }
}
```

Type  *Result* will work fine and there will be no typescript error as well as value and error properties and optional, but did you noticed we introduced some run time bug. As *value* is optional, I can remove it from the positive scenario as below and the function still be type correct but in actual care we want the value if success is *true*

```tsx
function getPersonalInfo(email: string): Result {
  const info = personalInfoCollection[email]
  if (info) {
    return {
      success: true,
      // value: info,
    }
  } else {
    return {
      success: false,
      error: 'Information not found',
    }
  }
}
```

To make sure we return the value when success is true, we can modify our Result Type something like below

```tsx
type Result =
  | {
      success: true
      value: PersonalInfo
    }
  | {
      success: false
      error: string
    }
```

Here we have modeled the *Return* type to reflect specific object structure. Now our function will throw type error if we remove value property because it is not an optional property now. same way error is also not optional.

Every discriminated union types needs a discriminant property to distinguish between the various alternatives. That discriminant property must be of literal type. In our case, we are using the success property as discriminant which is of a Boolean literal type

We can refactor the this code a bit more and make our *Result* type generic so the it can work with other types as well

```tsx
type Result<T> =
  | {
      success: true
      value: T
    }
  | {
      success: false
      error: string
    }

function getPersonalInfo(email: string): Result<PersonalInfo> {
  const info = personalInfoCollection[email]
  if (info) {
    return {
      success: true,
      value: info,
    }
  } else {
    return {
      success: false,
      error: 'Information not found',
    }
  }
}
```

When we use this function interesting things happen, we check if personal information is available by checking success property 

```tsx
const info = getPersonalInfo('manish@kumar.com')
if (info.success) {
  console.log(info.value)
  info.error // TSC Will throw error
} else {
  console.log(info.error)
}
```

If success is true we will be able to access only value property only,  we can use error property here beside Typescript compiler will throw error if we try to do it. 

Similarly, in else case we will be able to access only error property.

Using discriminated unions this way can really help you write fewer bugs. The type system forces you to check the discriminant property first before it gives you access to the individual properties. Note that for all of this to work properly, you should have these strict all checks compiler option set to true 

That's all for this part. Let me know in comments what you think about it.