Reputation: 8589
I am using lodash on a TypeScript app and I am getting an error
Cannot invoke an expression whose type lacks a call signature. Type '((callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[]) | ((callbackfn: (value: Phone, index: number, array: Phone[]) => U, thisArg?: any) => U[])' has no compatible call signatures.ts(2349)
Function:
{get(userInfo, 'phone', []).map((item, index) => (
<View style={styles.userPhone} key={index}>
{item.type === 'Desk' && <Text>Phone: {item.num}</Text>}
{item.type === 'Mobile' && <Text>Mobile: {item.num}</Text>}
</View>
))}
If I do this it works correctly:
{userInfo.phone &&
userInfo.phone.map((item, index) => (
<View style={styles.userPhone} key={index}>
{item.type === 'Desk' && <Text>Phone: {item.num}</Text>}
{item.type === 'Mobile' && <Text>Mobile: {item.num}</Text>}
</View>
))}
So what could I be missing?
PS:
Lodash get
docs: https://lodash.com/docs/#get
I have installed types package for lodash -> https://www.npmjs.com/package/@types/lodash
The thing is that I can not do it like this {userInfo.phone && userInfo.phone.map(...)}
because we are enforced to use get
for this type of things.
Upvotes: 0
Views: 10482
Reputation: 328758
Since I don't have lodash typings available, I'll just assume that the get()
function behaves more or less as follows:
type ReplaceUndefined<T, V> = T extends undefined ? V : T;
declare function get<T, K extends keyof T, V>(
obj: T,
key: K,
dflt: V
): ReplaceUndefined<T[K], V>;
This means that if key
of type K
is a key of obj
of type T
, then get()
will return a value of type T[K]
... unless that value may be undefined
, in which case it will replace undefined
with the type of the dflt
value, like this:
const val = get({ a: Math.random() < 0.5 ? "a" : undefined }, "a", 123); // string | number
Also, I'm asssuming the following type and value declarations:
interface Phone {
type: string;
num: string;
}
declare const userInfo: { phone?: Phone[] };
Now I'm ready to start answering the question. When I try to call get()
like this, we get an error:
get(userInfo, "phone", []).map((item, index) => "Number: " + item.num); // error!
// "cannot invoke an expression that lacks a call signature"
The problem is that the default value []
is inferred by the compiler to be of type never[]
. This the return value of get()
will be the union type Phone[] | never[]
, and thus the map
method of this union will itself be a union of the method types for Phone[]
and for never[]
. It is a longstanding issue in TypeScript that you usually can't call unions of functions. This has been improved in recent language versions, but there are still limitations, such as when more than one function in the union is generic. And since map()
is generic, TypeScript doesn't know how to call it on Phone[] | never[]
.
The obvious fix here is that you don't want []
to be treated as never[]
; instead you'd like it to be treated as an empty Phone[]
. If you can do that, then what comes out of get()
will be Phone[] | Phone[]
, which is just Phone[]
, and therefore its map()
method is no longer a union type, and is callable.
Here are two ways to do it... one is with a type assertion:
get(userInfo, "phone", [] as Phone[]).map((item, index) => "Number: " + item.num); // okay
and the other is with a type annotation on a new variable:
const noPhones: Phone[] = [];
get(userInfo, "phone", noPhones).map((item, index) => "Number: " + item.num); // okay
Okay, hope that helps. Good luck!
Upvotes: 5