Filtering TypeScript value types
Tags: typescript, webdev
Published: 2023-06-29 20:23:47

**Written by [Stanley Ulili](https://blog.logrocket.com/author/stanleyulili/)✏️**

TypeScript is an invaluable tool for writing safe code. It catches bugs early and provides warnings for issues that can cause exceptions during runtime. TypeScript will infer the type of data you are working with and save you from writing a lot of explicit type annotations in your code. 

Sometimes, there are cases when TypeScript does not infer the value correctly, and this often happens when filtering an array containing data of different types. These can result in TypeScript providing warnings for valid code, which can be confusing. 

In this tutorial, we will filter an array containing data of mixed types using the `filter()` method and ensure that TypeScript infers the values properly. 

_Jump ahead:_

*   [Prerequisites](#prerequisites)
*   [How to filter elements in TypeScript](#filter-elements-typescript)
*   [Filtering an array of different element types in TypeScript](#filtering-array-different-element-types-typescript)
*   [Filtering using a custom type guard with a type predicate](#filtering-using-custom-type-guard-type-predicate)
*   [Issues with custom type guards](#issues-custom-type-guards)

## Prerequisites

To follow this tutorial, you will need:

*   TypeScript set up on your system
    *   To learn how to set it up, visit the [documentation page](https://www.typescriptlang.org/download). If you are unable to set it up or don't want to, you can use the [TypeScript playground](https://www.typescriptlang.org/play)
*   Familiarity with [interfaces and type guards](https://blog.logrocket.com/understanding-using-interfaces-typescript/) in TypeScript
*   A good text editor that can show you the errors before compiling, such as [VS Code](https://code.visualstudio.com/), which has built-in TypeScript support

## How to filter elements in TypeScript <a name="filter-elements-typescript">

One of the most common tasks in programming is filtering an array. JavaScript comes with the [`filter()` method](https://blog.logrocket.com/javascript-array-group-array-grouptomap/) that filters an array based on the given criteria or condition and returns a new array with only the elements that passed the condition given. 

Assuming you have an array with numbers:

```typescript
const numbers = [10, 15, 20, 30, 40];
```

If you want to create a new array with only numbers greater than 20, you can filter the elements as follows:

```typescript
const greaterThanTwenty = numbers.filter((number) => {
  return number > 20;
});

console.log(greaterThanTwenty);
// output
[30, 40] 
```

The `filter()` method takes a callback that checks if a number is greater than `20`, and that returns `true` if the condition is satisfied. The method then returns an array containing only the elements that satisfied the condition.

TypeScript can infer the type of the new array returned by the `filter()` method when you hover on the `greaterThanTwenty` variable in your editor or using the TypeScript playground: ![TypeScript infers the variable as a number](http://blog.logrocket.com/wp-content/uploads/2023/06/typescript-infers-variable-number.png) Below is another example that handles complex data. In it, we define a `Doctor` interface to represent objects that are in the `doctors` array. We then use the `filter()` method to return objects that have a `specialty` property set to `Cardiology`:

```typescript
interface Doctor {
  type: "DOCTOR";
  name: string;
  specialty: string;
}

const doctors: Doctor[] = [
  { type: "DOCTOR", name: "John Doe", specialty: "Dermatology" },
  { type: "DOCTOR", name: "Jane Williams", specialty: "Cardiology" },
];

const cardiologists = doctors.filter(
  (doctor) => doctor.specialty == "Cardiology"
);
```

When we hover over the `cardiologists` variable, we will notice that TypeScript infers that the array contains objects matching the `Doctor` interface: ![TypeScript infers the variable as of type doctor](http://blog.logrocket.com/wp-content/uploads/2023/06/typescript-infers-variable-type-doctor.png) As we have seen, TypeScript can infer types when using the `filter()` method without any issues — because the arrays we have looked at so far contain elements of the same type. In the next section, we will look at filtering an array with elements of different types.

## Filtering an array of different element types in TypeScript <a name="filtering-array-different-element-types-typescript">

In this section, we will create a small program that contains an array of elements of different types to see the issues that will arise during TypeScript inference. 

To begin, create two interfaces, `Doctor` and `Nurse`:

```typescript
interface Doctor {
  type: "DOCTOR";
  name: string;
  specialty: string;
}

interface Nurse {
  type: "NURSE";
  name: string;
  nursingLicenseNumber: string;
}

type MedicalStaff = Doctor | Nurse;
```

In the example, you define two interfaces, `Doctor` and `Nurse`, to represent objects that will be in the array that we will soon define. The `Doctor` interface represents an object with `type`, `name`, and `specialty` fields; the `Nurse` interface has fields `type`, `name`, and `nursingLicenseNumber`. 

To store objects that can be represented by either the `Doctor` or `Nurse` interface, we define a [union type](https://blog.logrocket.com/understanding-discriminated-union-intersection-types-typescript/) `MedicalStaff` in the last line. 

Next, create an array containing objects that match the union type `MedicalStaff`:

```typescript
...
const doctor1: MedicalStaff = {
  type: "DOCTOR",
  name: "John Doe",
  specialty: "Dermatology",
};
const doctor2: MedicalStaff = {
  type: "DOCTOR",
  name: "Jane Williams",
  specialty: "Cardiology",
};

const nurse1: MedicalStaff = {
  type: "NURSE",
  name: "Bob Smith",
  nursingLicenseNumber: "RN123456",
};
const nurse2: MedicalStaff = {
  type: "NURSE",
  name: "Alice Johnson",
  nursingLicenseNumber: "RN654321",
};

const staffMembers = [doctor1, doctor2, nurse1, nurse2];
```

We created a `staffMembers` array, which contains four objects. The first two objects are represented by the `Doctor` interface, while the other two are represented by the `Nurse` interface. 

Following this, filter out the objects that don't have the `nursingLicenseNumber` property:

```typescript
...
const nurses = staffMembers.filter((staff) => "nursingLicenseNumber" in staff);
const nursingLicenseNumbers = nurses.map((nurse) => nurse.nursingLicenseNumber);
console.log(nursingLicenseNumbers);
```

In the first line, we filter out all the objects that don't have a `nursingLicenseNumber` property, which are the objects corresponding to the `Doctor` interface. Next, we use the `map()` method to return only the nursing license numbers in the `nursingLicenseNumbers` variable. 

After writing the code, you will notice a warning: ![Screenshot of a TypeScript warning nursingLicenseNumber property](http://blog.logrocket.com/wp-content/uploads/2023/06/typescript-warning-nursinglicensenumber-property.png) Upon close inspection, the warning is similar to the following:

```typescript
// Error
Property 'nursingLicenseNumber' does not exist on type 'Doctor | Nurse'.
  Property 'nursingLicenseNumber' does not exist on type 'Doctor'.
```

This is because the inferred type for the `nurses` variable is `const nurses: (Doctor | Nurse)[]`, even though the `filter()` method removes the objects without the `nursingLicenseProperty`. To make matters worse, if you hover over the `nursingLicenseNumbers` property, TypeScript infers it as `const nursingLicenseNumbers: any[]`. 

In ideal cases, the `nurses` variable should be inferred as `const nurses: Nurse[]`. That way, TypeScript wouldn't flag `nurse.nursingLicenseNumber` in the `map()` method.

## Filtering using a custom type guard with a type predicate <a name="filtering-using-custom-type-guard-type-predicate">

So far, we’ve learned TypeScript flags `nurse.nursingLicenseNumber` because the property is not available in the `Doctor` interface. To get around this error, we need to use a [custom type guard](https://blog.logrocket.com/how-to-use-type-guards-typescript/) with a type predicate. 

A custom type guard can check for almost any type we define ourselves, in comparison to other type guards, such as `typeof`, which are limited to a few built-in types. 

In our example, add a type predicate of `staff is Nurse` to the `filter()` method as an explicit return type annotation:

```typescript
const nurses = staffMembers.filter(
  (staff): staff is Nurse => "nursingLicenseNumber" in staff
);
const nursingLicenseNumbers = nurses.map((nurse) => nurse.nursingLicenseNumber);
console.log(nursingLicenseNumbers);
```

The type predicate `staff is Nurse` tells TypeScript that that function passed to the `filter()` method will be of type `Nurse`. 

If you hover over the `nurses` variable now, TypeScript will infer it as `:Nurse[]`:

```typescript
const nurses: Nurse[] = staffMembers.filter(
  (staff): staff is Nurse => "nursingLicenseNumber" in staff
);
```

And when you hover over the `nursingLicenseNumbers`, TypeScript will infer it properly as `string[]` instead of `any[]`:

```typescript
const nursingLicenseNumbers: string[] = nurses.map((nurse) => nurse.nursingLicenseNumber);
```

Another recommended way to handle this is to define a [type guard function](https://blog.logrocket.com/how-to-use-type-guards-typescript/). A type guard function has:

*   A return value that evaluates to a boolean
*   A type predicate

The following code uses a `isStaff` type guard function:

```typescript
const isStaff = (staff: MedicalStaff): staff is Nurse =>
  "nursingLicenseNumber" in staff;

const nurses = staffMembers.filter(isStaff);
const nursingLicenseNumbers = nurses.map((nurse) => nurse.nursingLicenseNumber);
console.log(nursingLicenseNumbers)
```

In the example, you define the `isStaff()` type guard function from the `filter()` function in the previous example to the `isStaff` function. You then call `filter()` with the `isStaff()` function as an argument. 

You can reuse the `isStaff()` function in other places that need a similar type predicate. 

If you want to access properties that are specific to the `MedicalStaff` type, you can use the `isStaff` type guard in an `if` statement:

```typescript
const surgicalNurse: MedicalStaff = {
  type: "NURSE",
  name: "Jane Doe",
  nursingLicenseNumber: "RN479312",
};

if (isStaff(surgicalNurse)) {
  console.log(surgicalNurse.nursingLicenseNumber);
}
```

TypeScript will narrow the type down to the `Nurse` type. You can confirm that by hovering over the `surgicalNurse` variable: ![Hover over the surgicalNurse variable to confirm the type narrowing](http://blog.logrocket.com/wp-content/uploads/2023/06/hover-over-surgicalNurse-variable.png) 

## Issues with custom type guards <a name="issues-custom-type-guards">

A custom type guard can fix a lot of issues when filtering an array of mixed types. However, it is not an infallible solution and has an issue you need to be aware of to make the best use of it. 

The issue is that TypeScript does not care if the implementation is correct. This can give you a false sense of comfort that you are writing safe code. 

Take the following example:

```typescript
const isStaff = (staff: any) : staff is Nurse => true;

const nurses = staffMembers.filter(isStaff);
const nursingLicenseNumbers = nurses.map(nurse => nurse.nursingLicenseNumber);
console.log(nursingLicenseNumbers)
```

The `isStaff()` function doesn’t check if any of the objects in the array has a `nursingLicenseNumber` property, and yet TypeScript doesn't warn us. This misleads us into believing that the implementation is correct. 

This may look like a bug in TypeScript, but this is a design choice, which can find in [TypeScript’s GitHub issue.](https://github.com/microsoft/TypeScript/issues/29980) 

To ensure you have safe code, you should review the type guard function implementation to ensure that they are well implemented because TypeScript doesn't have a built solution that can flag this issue.

## Conclusion

In this tutorial, you learned how to filter values in an array using TypeScript. First, you learned how to filter an array with elements of identical type. You then proceeded to filter an array containing elements of mixed types which brought up inference issues. From there, we used a custom type guard to help TypeScript properly infer an array of mixed types during filtering. To continue your TypeScript journey, visit [our blog archives](https://blog.logrocket.com/tag/typescript/).

---

##Get set up with LogRocket's modern Typescript error tracking in minutes:

1.Visit https://logrocket.com/signup/ to get an app ID.
2.Install LogRocket via NPM or script tag. `LogRocket.init()` must be called client-side, not server-side.

NPM:
```bash
$ npm i --save logrocket 

// Code:

import LogRocket from 'logrocket'; 
LogRocket.init('app/id');
```
Script Tag:
```javascript
Add to your HTML:

<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
```

3.(Optional) Install plugins for deeper integrations with your stack:
*   Redux middleware
*   ngrx middleware
*   Vuex plugin

[Get started now](https://lp.logrocket.com/blg/signup)