Creating different Typescript function type signatures based on types bound to generics on use
Tags: typescript
Published: 2022-05-08 17:13:29

---
title: Creating different Typescript function type signatures based on types bound to generics on use
published: true
description: 
tags: typescript
//cover_image: https://direct_url_to_image.jpg
---

Just wanted to make a note of this more than anything else. I spent some time the other day trying to figure out how I can make "one type" for all my API client functions. After a bit of struggling, I figured I can't really have them all `extend` something more generic as… well, they're different types of functions:

- Some take no args
- Some take only the resource id
- Some take only the request body
- Some take both the resource id and the request body

… and they all return the same type (of course) - which is not really important here.

Finally, I decided to look into conditional types in TS. I've known about this feature but hadn't actually used it in a "real" code-base. I still have to get my head around "naked" types and what-not, but basically, managed to get a good enough solution (for me) with this:

```ts
export type ApiClientFn<TId, TReqBody, TResBody> = [TId, TReqBody] extends [
  undefined,
  undefined,
]
  ? () => ApiClientRes<TResBody>
  : [TId, TReqBody] extends [number, undefined]
  ? (id: number) => ApiClientRes<TResBody>
  : [TId, TReqBody] extends [undefined, TReqBody]
  ? (reqBody: TReqBody) => ApiClientRes<TResBody>
  : [TId, TReqBody] extends [number, TReqBody]
  ? (id: number, reqBody: TReqBody) => ApiClientRes<TResBody>
  : never;
```

Basically, I can now create the different variations of API client function types using `ApiClientFn` - passing it 3 arguments to bind the generic types:

```ts
const getOne: ApiClientFn<number, TReqBody, TResBody> = async (id) => { // …

const getList: ApiClientFn<undefined, TReqBody, TResBody> = async () => { // …

const create: ApiClientFn<undefined, TReqBody, TResBody> = async (reqBody) => { // …

export const update: ApiClientFn<number, TReqBody, TResBody> = async (
  id,
  requestBody,
) => { // …
```

Wasn't what I initially had in mind. I initially set out to have one type to represent "any of the API client functions". Turns out I didn't actually need that type though. I had intended to use it in a `useQueryApi` function - in which I wanted to have one hook that takes any of my getter client API functions and uses it in a "common workflow" i.e. in order to DRY up concerns like throwing an `Error` on unsuccessful queries so `react-query`'s `useQuery` works properly and redirecting to login page and logging out (and notification) etc… on specific status code responses.

Turns out, I didn't need a type for "all API client functions I have", because this code just cared about the return type really - so it ended up taking:

```ts
export type ApiFnWrapper<TResBody> = () => ApiClientRes<TResBody>;
``` 

… and if the actual API client function takes any args, then they can always be closed over, like `id` is in `() => getOne(id)`, below:

```ts
  export const useSomeResource = (id: number, queryKey: string = 'queryId') => {
  const { data, ...rest } = useQueryApi(() => getOne(id), {
    queryKey,
  });

  return {
    data: data?.data,
    ...rest,
  };
};
```

- - -

Anyhow, the `ApiClientFn` "factory type function" - or whatever it's technical term is, allows me to:

- see all type signatures of my API client functions in one place
- not have to stay coming up with silly names for the different variations e.g. `type APIClientFnThatTakesAnId = (id: number) => ApiClientRes<TResBody>`

… and that seems like a win to me.