Prateek Surana
Prateek Surana

Reputation: 692

TypeScript make function output dependent on whether an input parameter is undefined or not

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

Answers (2)

Vetterjack
Vetterjack

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.

enter image description here

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

James Elderfield
James Elderfield

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

Related Questions