Issue
This Content is from Stack Overflow. Question asked by bourkison
I have a function which queries my database, and based on whether we are pulling “saved” data or “unsaved” data will change the output. I am wanting to keep this condensed into one function and so am working with Conditional Types to let Typescript know that the response of the function will change based on the type.
If saved, the function should return an array of ProductType (a custom type I have made), otherwise it should return an array of strings.
type QueryResponse<T> = T extends 'unsaved'
? ProductType[]
: T extends 'saved'
? string[]
: never;
export async function queryDb<T extends 'saved' | 'unsaved'>(type: T): Promise<QueryResponse<T>> {
...
if (type === 'unsaved') {
let response: ProductType[] = dbResult.data;
return response;
} else {
let response: string[] = dbResult.data;
return response;
}
}
I get an error under both return statements saying: Type 'ProductType[]' is not assignable to type 'QueryResponse<T>'.
, and Type 'string[]' is not assignable to type 'QueryResponse<T>'.
respectively.
Solution
Unfortunately, while your conditional type QueryResponse<T>
correctly infers the return type from T
, TypeScript is not able to leverage your type === 'saved'
condition as a type guard:
// Structure the data correctly to be in line with the types, then return.
if (type === 'saved') {
let response: string[] = dbResult.data;
type // type is NOT narrowed down to "saved" type
//^? (parameter) type: T extends "saved" | "unsaved"
return response; // Error: Type 'string[]' is not assignable to type 'QueryResponse<T>'.
} else {
let response: ProductType[] = dbResult.data;
type
//^? (parameter) type: T extends "saved" | "unsaved"
return response; // Error: Type 'ProductType[]' is not assignable to type 'QueryResponse<T>'.
}
As suggested by smac89 in the question comments, you could just skip the block that tries to type the return, since it is already specified by your explicit return type:
export async function queryDb0<T extends 'saved' | 'unsaved'>(
type: T,
): Promise<QueryResponse<T>> {
// Etc
// We may even skip typing the return here, it is already done in the explicit return type
return dbResult.data;
}
In your case, you could even avoid the conditional type, by simply using a function overload instead:
export async function queryDb2(type: 'saved'): Promise<string[]>
export async function queryDb2(type: 'unsaved'): Promise<ProductType[]>
export async function queryDb2(type: 'saved' | 'unsaved'): Promise<string[] | ProductType[]> {
// Etc.
// We may even skip typing the return here, it is already done in the overloads
return dbResult.data;
}
Let’s check its usage:
const s = queryDb2('saved');
// ^? const s: Promise<string[]>
const u = queryDb2('unsaved');
// ^? const u: Promise<ProductType[]>
const o2 = queryDb2('other'); // Error: No overload matches this call. Overload 1 of 2, [...] Argument of type '"other"' is not assignable to parameter of type '"saved"'. Overload 2 of 2, [...] Argument of type '"other"' is not assignable to parameter of type '"unsaved"'.
Looks good!
This Question was asked in StackOverflow by bourkison and Answered by ghybs It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.