Dynamic types in typescript

Viewed 1165

How would one define the return type of the fn function below?

const fn = (propName1: string, propName2: string) => {
  return { 
   [propName1]: () => true 
   [propName2]: () => 'abc'
  }
}
const x = fn('customProp1', 'customProp2')
console.log(x.customProp1)
console.log(x.customProp2)

e.g.

type FN = (propName: string)=> {
  [propName1]: ()=>true
  [propName2]: ()=>string
}

This is for two custom methods, but ideally the solution should allow for any number of custom methods and properties.

2 Answers

As long as propName is a string, you can do:

type FN_return = {[propName: string]: ()=>true};

If you want to be safe and are ok with being a little verbose, then use TypeScript Generics:

const fn = <T extends string>(propName: T): { [_ in T]: () => true } => {
  return { [propName]: () => true } as any; // a cast is sadly required for generic return types
}
const x = fn('customProp')
console.log(x.customProp) // ✅ works fine
x.notHere // ❌ compile error

Also, this will automatically support if you are passing in a generic string type with the unsafe counterpart that the other answer used.

const y = fn("foo" as string); // same as Record<string, () => true>
y.foo; // compiles but is valid
y.asdf; // compiles but is undefined

If you wanted to be extremely safe, then you could actually change your return type when someone passes in a string (not exact string type) using conditional types to show it could be undefined:

const fn = <T extends string>(propName: T): { [_ in T]: Exclude<undefined | (() => true), string extends T ? never : undefined> } => {
  return { [propName]: () => true } as any;
}
const x = fn("hello"); // { hello: () => true; }
x.asdf // ❌ compile error
const y = fn("hello" as string); // { [x: string]: (() => true) | undefined; }
y.foo // (() => true) | undefined
y.hello // (() => true) | undefined
Related