davidmh
davidmh

Reputation: 1378

How to match the right type for an array of Unions using Array.prototype.find?

I have the following types:

type A = { style: 'A', x: string }
type B = { style: 'B', y: string }

type Union = A | B;

I'm trying to find the element of type A in a Union[], like this:

const data: Union[] = [
    { style: 'A', x: 'test' },
    { style: 'B', y: 'test' },
]

const a: A | undefined = data.find(d => d.style === 'A');

But that throws the error:

Type 'A | B | undefined' is not assignable to type 'A | undefined'.
  Property 'x' is missing in type 'B' but required in type 'A'.

The predicate function for Array.prototype.find has no problems finding the right type, I can access the properties of A without any issues:

type A props

Is the return type that misses the refinement.


The only way I've been able to work around it is with a for loop, but that feels cumbersome and unnecessary.

let a: A | undefined;

for (let i = 0; i < data.length; i++) { 
    const val = data[i];
    if (val.style === 'A') { 
        a = val;
        break;
    }
}

Upvotes: 1

Views: 94

Answers (2)

kaya3
kaya3

Reputation: 51034

Your predicate d => d.style === 'A' is inferred as type (d: Union) => boolean.

For your desired behaviour, it would have to be inferred as (d: Union) => d is A, i.e. a user-defined type guard. This won't happen because Typescript never infers that a function is a user-defined type guard; if you want it to be, you have to explicitly declare it as one. If you write it like this, then it works:

function isA(d: Union): d is A {
    return d.style === 'A';
}

const a: A | undefined = data.find(isA); // no error

Actually, when I started writing this answer I going to say that this is probably impossible, because it would require Array.prototype.find's return type to depend on whether or not the predicate is a user-defined type guard. I didn't think that was possible, but apparently it is; I only discovered it works by testing it, so I learned something today, too.

Playground Link

Upvotes: 2

bela53
bela53

Reputation: 3485

The signature of Array.prototype.find is:

find<S extends T>(predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined;

Pay attention to the predicate callback return type: value is S. This passed-in predicate is already intended to narrow down your result. So no need to define an extra funtion, you can write it inline:

const a: A | undefined = data.find((d): d is A => d.style === 'A'); // works

Live example

Upvotes: 0

Related Questions