3

Can someone explain to me why in this case:

const dataValues: ValueRange[] = res.data.valueRanges.filter((range: ValueRange) => range.values);

const formattedValues: Array<SheetData | undefined> = dataValues.map(this.formatSheetRanges);

const sheetData: Array<SheetData> = formattedValues.filter((sheetData: SheetData | undefined) => sheetDataGuard(sheetData));

function sheetDataGuard(data: SheetData | undefined): data is SheetData {
  return !!(<SheetData>data).user;
}

The sheetData array will still complain that the type of it is

Array<SheetData | undefined>

but if i change the last filtering to:

const sheetData: Array<SheetData> = formattedValues.filter(sheetDataGuard);

the typescript doesn't complain anymore?

1 Answer 1

6

It's because the typings for the method Array<T>.filter() in the standard TypeScript lirbary have an overload signature that specifically narrows the returned array element type if the callback function is known to be a user-defined type guard function:

 interface Array<T> {
   filter<S extends T>(
     callbackfn: (value: T, index: number, array: T[]) => value is S, 
     thisArg?: any
   ): S[];
 }

Since the type of sheetDataGuard is assignable to (value: SheetData | undefined, index: number, array: Array<SheetData | undefined>) => value is SheetData, then calling filter on an Array<SheetData | undefined> with sheetDataGuard as the callbackfn parameter will cause the compiler to select that overload with SheetData inferred for S.

But when you call it with (sheetData: SheetData | undefined) => sheetDataGuard(sheetData) instead, the type of that function is inferred to return just boolean. That's because user-defined type guard functions do not propagate and are not inferred for you. A type like x is Y generally gets widened to boolean by the compiler as soon as you start doing things with it.

You could tell the compiler that your arrow function callback is also a type guard by using an arrow-function return type annotation, like this:

const sheetData: Array<SheetData> = formattedValues.filter(
    (sheetData: SheetData | undefined): sheetData is SheetData =>
        sheetDataGuard(sheetData)
);

And the compiler should be happy. Of course if you're going to do this, you might as well forget about defining sheetDataGuard as a separate function:

const sheetData: Array<SheetData> = formattedValues.filter(
(sheetData: SheetData | undefined): sheetData is SheetData =>
    !!sheetData
);

Anyway, hope that helps. Good luck!

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.