Filter undefined elements and make ESLint happy, too
Tags: typescript, eslint, eslintunicorn
Published: 2024-03-27 00:00:00

_This post is the result of multiple web searches, personal digging and many compromises_

Suppose you have the Product defined as shown below

```ts
type Product = {
  name: string;
  yearOfPurchase?: number | undefined;
}
```

and the following data

```ts
const products: Product[] = [
  { name: 'House', yearOfPurchase: 2021 },
  { name: 'BMX', },
  { name: 'Car', yearOfPurchase: 2020 },
  { name: 'Chair', yearOfPurchase: undefined },
];
```

Now you want to filter from the array the elements with the `yearOfPurchase` correctly set

```ts
const purchased = products
  .filter(({yearOfPurchase}) => yearOfPurchase !== undefined);
```

So finally you can access to the year without any check on it (for simplicity we assume the array is not empty)

```ts
const yearAt0 = purchased[0].yearOfPurchase;
const elapsedYears = new Date().getFullYear() - yearAt0;
```

WRONG!!! The error is still here `Object is possibly 'undefined'`

The returned type is always the same, `yearOfPurchase` is always declared as `yearOfPurchase?: number | undefined`

We can define a new type, where all properties are defined as mandatory

```ts
type SafeYear = Required<Product>;
```

and create a user-defined type guard

```ts
function isSafeYear(v: Product): v is SafeYear {
  return v.yearOfPurchase !== undefined;
}
```

then use it with `filter()`

```ts
const purchased = products.filter(isSafeYear);
```

and finally

```ts
const yearAt0 = purchased[0].yearOfPurchase;
const elapsedYears = new Date().getFullYear() - yearAt0;
```

compiles without errors

### ...but ESLint Unicorn disagrees

If you use ESLint and turned on the Unicorn rule [Prevent passing a function reference directly to iterator methods](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-callback-reference.md)

Another problem occurs

    ESLint: Do not pass function `isSafeYear` directly to `.filter(…)`.(unicorn/no-array-callback-reference)

So you modify the code to be compliant with ESLint

```ts
const purchased = products.filter(e => isSafeYear(e));
```

But again `Object is possibly 'undefined'`

The returned type by the passed callback is `Product` so we need to explicitly declare the type

```ts
const purchased = products.filter((e): e is SafeYear => isSafeYear(e));
```

and finally

```ts
const elapsedYears = new Date().getFullYear() - purchased[0].yearOfPurchase;
```

works again

### Fields are required but values accept undefined

Consider the type defined as below

```ts
type Product = {
  name: string;
  yearOfPurchase: number | undefined;
}
```

Pay attention, `yearOfPurchase` now is required and no longer declared as `yearOfPurchase?` but the value as before accepts `undefined`

The `SafeYear` type no longer removes the `undefined` because all fields are required, so the code below stopped AGAIN to compile, GRRRRRR!!!

```ts
const yearAt0 = purchased[0].yearOfPurchase;
const elapsedYears = new Date().getFullYear() - yearAt0;
```

The standard [Utility Types](https://www.typescriptlang.org/docs/handbook/utility-types.html) contains the [`NonNullable<Type>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#nonnullabletype) but it doesn't work on fields, only on direct types, so we need to create our own version

```ts
type NonNullableFields<T> = {
  [P in keyof T]: NonNullable<T[P]>;
};
```

and change the `SafeYear` definition

```ts
type SafeYear = NonNullableFields<Product>;
```

and the code compiles again