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;
}
and the following data
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
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)
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
type SafeYear = Required<Product>;
and create a user-defined type guard
function isSafeYear(v: Product): v is SafeYear {
return v.yearOfPurchase !== undefined;
}
then use it with filter()
const purchased = products.filter(isSafeYear);
and finally
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
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
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
const purchased = products.filter((e): e is SafeYear => isSafeYear(e));
and finally
const elapsedYears = new Date().getFullYear() - purchased[0].yearOfPurchase;
works again
Fields are required but values accept undefined
Consider the type defined as below
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!!!
const yearAt0 = purchased[0].yearOfPurchase;
const elapsedYears = new Date().getFullYear() - yearAt0;
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]>;
};
and change the SafeYear
definition
type SafeYear = NonNullableFields<Product>;
and the code compiles again
Top comments (0)