DEV Community

Davide Ficano
Davide Ficano

Posted on

Filter undefined elements and make ESLint happy, too

This post is the result of multiple web searches, personal digging and many compromises

Suppose you have the Product defined as shown below

type Product = {
  name: string;
  yearOfPurchase?: number | undefined;
}
Enter fullscreen mode Exit fullscreen mode

and the following data

const products: Product[] = [
  { name: 'House', yearOfPurchase: 2021 },
  { name: 'BMX', },
  { name: 'Car', yearOfPurchase: 2020 },
  { name: 'Chair', yearOfPurchase: undefined },
];
Enter fullscreen mode Exit fullscreen mode

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

const purchased = products
  .filter(({yearOfPurchase}) => yearOfPurchase !== undefined);
Enter fullscreen mode Exit fullscreen mode

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

const yearAt0 = purchased[0].yearOfPurchase;
const elapsedYears = new Date().getFullYear() - yearAt0;
Enter fullscreen mode Exit fullscreen mode

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

type SafeYear = Required<Product>;
Enter fullscreen mode Exit fullscreen mode

and create a user-defined type guard

function isSafeYear(v: Product): v is SafeYear {
  return v.yearOfPurchase !== undefined;
}
Enter fullscreen mode Exit fullscreen mode

then use it with filter()

const purchased = products.filter(isSafeYear);
Enter fullscreen mode Exit fullscreen mode

and finally

const yearAt0 = purchased[0].yearOfPurchase;
const elapsedYears = new Date().getFullYear() - yearAt0;
Enter fullscreen mode Exit fullscreen mode

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

Another problem occurs

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

So you modify the code to be compliant with ESLint

const purchased = products.filter(e => isSafeYear(e));
Enter fullscreen mode Exit fullscreen mode

But again Object is possibly 'undefined'

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

const purchased = products.filter((e): e is SafeYear => isSafeYear(e));
Enter fullscreen mode Exit fullscreen mode

and finally

const elapsedYears = new Date().getFullYear() - purchased[0].yearOfPurchase;
Enter fullscreen mode Exit fullscreen mode

works again

Fields are required but values accept undefined

Consider the type defined as below

type Product = {
  name: string;
  yearOfPurchase: number | undefined;
}
Enter fullscreen mode Exit fullscreen mode

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!!!

const yearAt0 = purchased[0].yearOfPurchase;
const elapsedYears = new Date().getFullYear() - yearAt0;
Enter fullscreen mode Exit fullscreen mode

The standard Utility Types contains the NonNullable<Type> but it doesn't work on fields, only on direct types, so we need to create our own version

type NonNullableFields<T> = {
  [P in keyof T]: NonNullable<T[P]>;
};
Enter fullscreen mode Exit fullscreen mode

and change the SafeYear definition

type SafeYear = NonNullableFields<Product>;
Enter fullscreen mode Exit fullscreen mode

and the code compiles again

Top comments (0)