DEV Community

Cover image for Typesafe multiple return types based on parameter value with Typescript.
Krisztián Maurer
Krisztián Maurer

Posted on • Edited on

Typesafe multiple return types based on parameter value with Typescript.

Typescipt types and runtime values are two separate levels, basically writing typescript conditions based on values is not supported, but there is a solution.

What I would like to achieve is the following: A function return type changes based on the value entered in the function parameter.

function getUsers(withPagination: boolean): IUser[] | IPaginatedUsers { 
  // ... 
  return users; 
}
Enter fullscreen mode Exit fullscreen mode

The purpose of this function is to return users from an api, if withPagination is true then it wraps the user array in pagination, if false then only the user array is returned (just an example).

The problem is that runtime reveals the return value based on the "withPagination" variable the return value is not specific, but we know what it will be, it can cause some headache.

One solution would be to specify the return value generically, but that is not very convenient and can be messed up. The other solution is to create two separate functions, which in this case might be better but it can cause code duplication.

function getUsers(): IUser[] { 
  // ... 
  return users; 
}

function getUserswithPagination(): IPaginatedUsers { 
  // ... 
  return users; 
}

Enter fullscreen mode Exit fullscreen mode

If we want to keep it together and implement 2 or more different returns in a typesafe way, then you can do it this way:

interface IUser {
    name: string,
    age: number,
}

interface IPaginated<T> {
    data: T[],
    meta: {
        page: number,
        limit: number,
        totalPages: number
    }
}

interface IResponseOptions {
    withPagination: boolean
}

const DEFAULT_RESPONSE_OPTION = {
    withPagination: true
}

type UserResultType<T extends IResponseOptions> = T extends {
    withPagination: true
} ? IPaginated<IUser> : IUser[];


function getUsers<T extends IResponseOptions>(withPagination: T = DEFAULT_RESPONSE_OPTION as any): UserResultType<T> { 
  // fetch data..
  return "something" as any as UserResultType<T>; 
}



getUsers({withPagination: true}); // return IPaginated<IUser>
getUsers({withPagination: false}); // return IUser[];
getUsers(); // returns bsed on DEFAULT_RESPONSE_OPTION: IPaginated<IUser>


Enter fullscreen mode Exit fullscreen mode

Top comments (5)

Collapse
 
codeedog profile image
Andrew Philips • Edited

I like where you're going with this. One issue, your proposal doesn't quite address everything I think you'd hope for. If we assign the return value to a variable, we can see that the return type doesn't match for one of them.

var foo: IPaginated<IUser> = getUsers({withPagination: true}); // ok
var baz: Paginated<IUser>  = getUsers(); // error
var qux: IPaginated<IUser> | IUser[] = getUsers(); // ok

Enter fullscreen mode Exit fullscreen mode

I'm not sure how to fix it.

TS Playground

Collapse
 
maurerkrisztian profile image
Krisztián Maurer

Yes, you are right. I found a solution for this:

const DEFAULT_RESPONSE_OPTION: { withPagination: true } = {
    withPagination: true
}
Enter fullscreen mode Exit fullscreen mode

Add an explicit interface { withPagination: true } beacaues the typeof DEFAULT_RESPONSE_OPTION is boolean without this. (idk how to get the explicit type of a constant with type inference)

Then add typeof DEFAULT_RESPONSE_OPTION as the default value of the T generic.

function getUsers<T extends IResponseOptions = typeof DEFAULT_RESPONSE_OPTION>(...)
Enter fullscreen mode Exit fullscreen mode

TS Playground

Collapse
 
codeedog profile image
Andrew Philips • Edited

Nice. Thank you.

I tried modifying your DEFAULT_RESPONSE_OPTION slightly with something I recently learned about Literal Types and Literal Inference, specifically the type modifier as const.

const DEFAULT_RESPONSE_OPTION = { withPagination: true } as const;
Enter fullscreen mode Exit fullscreen mode

TS Playground

Thread Thread
 
maurerkrisztian profile image
Krisztián Maurer

Very nice! 🎉 Thanks

Collapse
 
maurerkrisztian profile image
Krisztián Maurer

I found a cleaner way to achieve this with function overloads Ts Playground