Saeed Mosavat
Saeed Mosavat

Reputation: 138

Infer type based on the generic type of a sibling property in Typescript

I want to define the type of a property based on the generic type of its sibling property.

for example, assume we have:

type Person = {
  id: number;
  name: string;
}

type Select<Value=unknown> = (props:Props<Value>) => any;

const PersonSelect:Select<Person> = (props) => null //implementation is irrelavant
const TextSelect:Select<string> = (props) => null //implementation is irrelavant


then I want to be able to do something like this:


type Filter<V = unknown > = {
  component: Select<V>,
  transformer: (value: V) => string
};

const filters:Array<Filter> = [
  {
    component: PersonSelect,
    transformer: (selected) => selected.name //type of `selected` should be infered as `Person`
  },
  {
    component: TextSelect,
    transformer: (selected) => selected // type of `selected` should be infered as `string`
  }
]


Workaround

With the above code I can do it like



const personFilter:Filter<Person> = {
    component: PersonSelect,
    transformer: (selected) => selected.name
}
const textFilter:Filter<string> = {
    component: TextSelect,
    transformer: (selected) => selected
}

const filters = [personFilter, textFilter];

But I'm looking for a solution that works without explicitly defining the type of each filter object. Also, the generic V type can be anything, so I can't use the union of all possible combinations.

Is there a better solution?

Upvotes: 3

Views: 1151

Answers (1)

Seems to be TypeScript having hard time to infer arrow function property argument based on other property of same object.

I was unable to do it with help of function inference.

The most simplest way is to make builder function which returns Filter<_>.


type Person = {
  id: number;
  name: string;
}

type Props<T> = T

type Select<Value = unknown> = (props: Props<Value>) => any;

const PersonSelect: Select<Person> = (props) => null //implementation is irrelavant

const TextSelect: Select<string> = (props) => null //implementation is irrelavant

type Filter<V = unknown> = {
  component: Select<V>,
  transformer: <T extends V>(value: T & V) => string
};


const builder = <V,>(
  component: Select<V>,
  transformer: (value: V) => string
): Filter<V> =>
  ({ component, transformer })

const filters = [
  builder(PersonSelect, (selected) => selected.name),
  builder(TextSelect, (selected) => selected)
]

Playground

Because component and transformer arguments are not the part of same data structure, it is easier to infer them and apply our type constraints.

Here, you can find more examples of type Inference on function arguments.

Upvotes: 2

Related Questions