TrevTheDev
TrevTheDev

Reputation: 2747

Dynamic types in typescript

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.

Upvotes: 4

Views: 6356

Answers (3)

TrevTheDev
TrevTheDev

Reputation: 2747

The best solution I've found so far is as follows:

type Fn<P1 extends string, P2 extends string>={ [K in P1]: 'whatever1' } & { [K in P2]: 'whatever2' }

type Solution1 = Fn<'customProp1','customProp2'> 
// { customProp1: "whatever1"; } & { customProp2: "whatever2"; }

type Identity<T> = T extends object ? {} & { [P in keyof T]: T[P] } : T

type Solution2 = Identity<Fn<'customProp1','customProp2'>> 
// { customProp1: "whatever1"; customProp2: "whatever2";}

Upvotes: 0

sno2
sno2

Reputation: 4213

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

Upvotes: 1

2pichar
2pichar

Reputation: 1378

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

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

Upvotes: 1

Related Questions