4

Following is the code I'm trying to run in a typescript editor

  type ABC = {
      title: string
  }

  type DEF = {
      name: string
  }


  type XYZ = {
      desc: ABC[] | DEF[]
  }


const container: XYZ = {
    desc: [{title: 'abc'},{title: 'def'}]
}
  const { desc } = container


desc.find((t: ABC) => t.title === 'abc') 

But there is red line under find() and when i hover over it I see the following message:

This expression is not callable.Each member of the union type 
'{ <S extends ABC>(predicate: (this: void, 
value: ABC, index: number, obj: ABC[]) => value is S, thisArg?: any): S | 
undefined; (predicate: (value: ABC, index: number, obj: ABC[]) => unknown, 
thisArg?: any): ABC | undefined; } | { ...; }' has signatures, but none of
 those signatures are compatible with each other.

How do i fix this such that I don't see the squiggly line

Inigo
  • 12,186
  • 5
  • 41
  • 70
The Third
  • 785
  • 2
  • 10
  • 30

3 Answers3

3

There are two problems: one yours, one Typescript's

  1. The type of desc is ABC[] | DEF[] (If you are using and IDE it should tell you this). This means that, to be type-safe, you need to supply a find predicate that can handle elements of type ABC | DEF, not just ABC as you have.

    For example, this would be type-safe:

    desc.find((t: ABC | DEF) => 'title' in t && t.title === 'abc')
    
    
  2. Even if you did supply such as type-safe find predicate, Typescript currently is unable to recognize it as type-safe. There is an open Typescript issue for this flaw.

Workaround

Since you aren't clear about this and haven't answered my question, I'm going to assuming the intent of your code is to ONLY find ABC elements with the right title, and ignore DEF elements even if their name property matches 'abc'.

the quick hack

The un-type-safe workaround would be to hardcode a type assertion:

// This isn't type-safe, but it will still do "the right thing"
// at runtime even if desc is really a DEF[], because t.title
// will be undefined for DEF elements.
(desc as ABC[]).find((t: ABC) => t.title === 'abc')

a type-safe solution

A type-safe workaround would make sure that desc is actually an ABC[] instead of a DEF[] before apply the find predicate to it:

type ABC = {
    title: string
}

type DEF = {
    name: string
}

type XYZ = {
    desc: ABC[] | DEF[]
}

const container: XYZ = {
    desc: [{ title: 'abc' }, { title: 'def' }]
}
const { desc } = container

let result: ABC | undefined

// only do the search if desc is an ABC[]
if (desc[0] && 'title' in desc[0]) {
    // Typescript isn't smart enough to figure out that
    // desc is now guaranteed to be an ABC[], we need
    // to include a type assertion
    result = (desc as ABC[]).find((t: ABC) => t.title === 'abc')
}

console.log(result)

You can also test it in TS Playground.

Inigo
  • 12,186
  • 5
  • 41
  • 70
0

You could use a templated type on the container, XYZ, since it seems that by the time you do the find you want to assert that desc is of type ABC[]. For example:

type ABC = {
    title: string
}

type DEF = {
    name: string
}

type XYZ<T extends ABC[] | DEF[]> = {
    desc: ABC[] | DEF[]
}

const container: XYZ<ABC[]> = {
    desc: [{title: 'abc'},{title: 'def'}]
}

const { desc } = container


desc.find((t: ABC) => t.title === 'abc') 
-1

I solved it by doing this

const { desc}: { desc: ABC[] } = container


desc.find((t: ABC) => t.title === 'abc') 
The Third
  • 785
  • 2
  • 10
  • 30
  • This answer does not work [as proven here in Typescript Playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAggQgYSgXigbwFBW1YBLYAGwgC4oBnYAJzwDsBzDAXww1EigBEBRAMRXRYctAIYBbUhWp1GLNuGgANAJoAtAZhxQAJhHIBjMvAQBtALpQAPlz7nmrfQHtalKE9rARdCFTIr1qJo4ugZkJmi4BMRkAOQiAEb6MVBMADTokUSSMboAZslMZvYY7q5oIfpMZBEVRojmKQLunt5UrBUAdLl02gAUvcB1CACUKAB8uB34WSjIqHGJMcNAA). It would be useful for the community if you answered my question at the top. Remember, [SO] users are helping you for free. The least you could do is answer their questions, and make sure your own provided answer works. – Inigo Jun 23 '22 at 14:52