Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: Return type of function based on input value (enum)

I'm storing some settings into local storage and I would like to type the responses when I get (and ideally also insert) values from/to the storage.

From what I've seen, the best way seems to be to use function overloading. So this is what I have now and it works:

export enum SettingsKey {
  hasOnboarded = 'hasOnboarded',
  phoneNumber = 'phoneNumber'
}

export async function getSetting(storage: Storage, key: SettingsKey.phoneNumber): Promise<string>
export async function getSetting(storage: Storage, key: SettingsKey.hasOnboarded): Promise<boolean>
export async function getSetting(storage: Storage, key: any) {
  return storage.get(key)
}

The thing that I don't like about this solution is that it's possible to forget adding a new element in the enum to the overload type definitions. Is there a way to enforce that all enum values are handled? Or is there maybe a better way to do this altogether?

I thought this would be a simple thing, a mapping from value hasOnboarded to return type boolean etc, but it's obviously not that easy.

It looks to me like conditional types might solve this problem, but I can't quite wrap my head around how it works.

I also saw this approach, but this seems like a little too much overhead.

Any insight would be greatly appreciated!

like image 369
Andreas Gassmann Avatar asked Oct 01 '18 14:10

Andreas Gassmann


Video Answer


1 Answers

You can use an extra type to map between the enum and the promise return type. We then add a generic parameter to getSettings that extends the SettingsKey enum and use the generic type to index into the mapping type. The generic parameter will be inferred based on the enum member we specify as an argument.

If the mapping type does not contain all keys of the enum we will get an error on the function.

export enum SettingsKey {
    hasOnboarded = 'hasOnboarded',
    phoneNumber = 'phoneNumber'
}

type SettingsKeyReturnType = {
    [SettingsKey.hasOnboarded]: boolean,
    [SettingsKey.phoneNumber]: string
}
export async function getSetting<K extends SettingsKey>(storage: Storage, key: K): Promise<SettingsKeyReturnType[K]> {
    return storage.get(key)
}
let a = getSetting(window.localStorage, SettingsKey.phoneNumber); // Promise<string>
let b = getSetting(window.localStorage, SettingsKey.hasOnboarded); // Promise<booelan> 

// We can also define a set function in a similar way
export async function setSetting<K extends SettingsKey>(storage: Storage, key: K, value: SettingsKeyReturnType[K]): Promise<void> {
   ///...
}
like image 85
Titian Cernicova-Dragomir Avatar answered Sep 17 '22 12:09

Titian Cernicova-Dragomir