Reputation: 692
I have a function that takes an optional object as a parameter which has a key called initialData
. The function does some processing and returns an object that contains a data
key which can be undefined
or can be of the generic type passed to the function.
Now what I want is that when initialData
is provided to the options data
would never be undefined
in the output.
Here is a contrived example of what I was able to figure out so far, but it's not working as expected:
interface Options<Data = unknown> {
initialData?: Data;
prop1?: string;
}
interface OptionsWithInitialData <Data = unknown> extends Options<Data> {
initialData: Data;
}
interface Result<Data = unknown> {
data?: Data;
key: string;
}
interface ResultWithData<Data = unknown> extends Result<Data> {
data: Data;
}
type PossibleOptions<Data> = Options<Data> | OptionsWithInitialData<Data>;
function doSomething<Data, TOptions extends PossibleOptions<Data>>(key: string, options?: TOptions): TOptions extends OptionsWithInitialData<Data> ? ResultWithData<Data> : Result<Data> {
return {
data: options?.initialData,
key,
}
}
const result = doSomething('test', { initialData: { foo: 'bar' } })
// This should not throw an error
console.log(result.data.foo)
export { doSomething };
Also here is a link to the playground if you want to give it a try in the TypeScript playground.
Would love to understand what I am doing wrong and if there's a better way to do this.
Upvotes: 0
Views: 204
Reputation: 2505
First of all, I modified your example playground a little bit to get rid of the default unknown
generics type and to make sure your specialized types definitely have initialData
and data
prop typed as TData
(I would personally avoid trying to override interface props).
Second, the issue here I guess is that TypeScript has no way to infer your TData
generic without manually specifying it on function call, so it falls back to unknown
for TData
.
So technically I guess your conditional type would work, but since TData
of your return type is unknown
, you can't really use it.
The difference to useQuery
hook is that TypeScript there can automatically infer the TData
type from the query functions return type.
Upvotes: 0
Reputation: 2507
You can do achieve this by making use of function polymorphism and declaring a few different signatures for the doSomething
function like,
interface Options {
prop1?: string;
}
interface OptionsWithInitialData<Data = unknown> extends Options {
initialData: Data;
}
interface Result<Data = unknown> {
data?: Data;
key: string;
}
interface ResultWithData<Data = unknown> extends Result<Data> {
data: Data;
}
// No initial data, and you'll maybe get data
function doSomething<Data>(key: string, options?: Options): Result<Data>;
// With initial data, you'll always get data
function doSomething<Data>(
key: string,
options: OptionsWithInitialData<Data>
): ResultWithData<Data>;
// Implementation signature
function doSomething<Data>(
key: string,
options?: Options | OptionsWithInitialData<Data>
): Result<Data> | ResultWithData<Data> {
return {
data: options && 'initialData' in options ? options?.initialData : undefined,
key,
};
}
Upvotes: 1