Bas
Bas

Reputation: 103

Typescript: How to narrow down function return type based on function parameter value

I'm fairly new to typescript and can't figure out a way to properly narrow down the return type of a function based on an argument of that same function.

Consider the following code:

import exampleApiResource from './resources/example';
import exampleApiResource2 from './resources/example2';
import { ApiResources } from './typings';

const apiResources: ApiResources = {
  exampleResource: exampleApiResource,
  exampleResource2: exampleApiResource2,
};

export function useApiClient(resource: keyof ApiResources) {
  return apiResources[resource];
}

export default apiResources;

/** typings **/
export type ApiResources = {
  exampleResource: ExampleType;
  exampleResource2: ExampleType2;
};

export type ExampleType = {
  getExample: () => Promise<TestResource>;
};

export type ExampleType2 = {
  getExample2: () => Promise<TestResource>;
};

I want to expose the apiResources object through the useApiClient function. The exampleApiResource and exampleApiResource2 hold different properties, as you can see in the typings.

Typescript infers the useApiClient function to return the following types: ExampleType | ExampleType2

How would one narrow this down to a specific type, is this even possible?

--

Use case:

Expose an axios based API client through a function. The function accepts a parameter which will resolve to an object which contains functions to perform API related actions (E.G: getPosts(), updatePost() etc).

Upvotes: 2

Views: 1214

Answers (1)

jcalz
jcalz

Reputation: 330571

You should make useApiClient() a generic function whose type parameter K corresponds to the particular member(s) of keyof ApiResources passed in as resource:

export function useApiClient<K extends keyof ApiResources>(resource: K) {
  return apiResources[resource];
}

Now the return type of the function is ApiResources[K], an indexed access type corresponding to the type of the property value of type ApiResources at a key of type K.


You can verify that this will behave as desired when you call useApiClient() and return either ExampleType or ExampleType2 depending on the input:

const ex = useApiClient("exampleResource"); // const ex: ExampleType
const ex2 = useApiClient("exampleResource2"); // const ex2: ExampleType2

You'll only get the union ExampleType | ExampleType2 in cases where the input type is also a union:

const exUnion = useApiClient(
  Math.random() < 0.5 ? "exampleResource" : "exampleResource2"
);
// const exUnion: ExampleType | ExampleType2

Playground link to code

Upvotes: 1

Related Questions