Mike
Mike

Reputation: 333

How to filter and execute with array of multiple types?

I have an array which may have two types A and B. I would like to perform actions over items of one type.

type A = {
  propA: number
}
type B = {
  propB: string
}
type MyArray = Array<A|B>
const anArrayOfBothAAndB: MyArray = [{ propA: 1 }, { propB: '2' }]

anArrayOfBothAAndB.filter((item) => {
  return (item as A).propA
})
.forEach((item: A) => { // reports error here
  console.log(item.propA)
})

I can add code like const itemA: A = item as any to make console.log(itemA.propA) work, but it looks not elegant.

Upvotes: 3

Views: 1824

Answers (2)

Alan
Alan

Reputation: 108

Well, since 2020, some things have changed in the TypeScript world. The filter method may be used with a generic parameter. Here's the perfect example, also with the filter method ;)

Given the code snippet you've pasted here.

type A = {
  propA: number
}
type B = {
  propB: string
}
type MyArray = Array<A|B>
const anArrayOfBothAAndB: MyArray = [{ propA: 1 }, { propB: '2' }]

anArrayOfBothAAndB.filter((item) => {
  return (item as A).propA
})
.forEach((item: A) => { // reports error here
  console.log(item.propA)
})

There should be a difference in the first filter() call, so it should be like this:

anArrayOfBothAAndB.filter<A>((item): item is A => {
    return 'propA' in item;
}).forEach((item) => {
    console.log(item.propA)
})

The difference is that you pass a predicate function (also called type guard) as the filter callback. So you give to the filter this guard which assures the new array that it's the type you predicate.

Upvotes: 0

101arrowz
101arrowz

Reputation: 1905

Your issue is that TypeScript isn't smart enough to detect that all elements of the array should be of type A after the filtration. You need to declare the return type of the filter function as item is A. See the documentation here.

Fixed version:

type A = {
  propA: number
}
type B = {
  propB: string
}
type MyArray = Array<A|B>
const anArrayOfBothAAndB: MyArray = [{ propA: 1 }, { propB: '2' }]

anArrayOfBothAAndB.filter((item): item is A => {
  return (item as A).propA !== undefined
})
.forEach((item) => { // no more error, don't even have to specify type
  console.log(item.propA)
})

Upvotes: 9

Related Questions