Home > Software design >  Typescript :: Related Generic Constraints
Typescript :: Related Generic Constraints

Time:05-30

In React, I have a generic interface like so:

interface BaseProps<T> {
  data: T;
}

The consumers of my code will extend this to define their own Props and Component:

interface MyProps extends BaseProps<string> {
  ...
}
const MyComponent: React.FC<MyProps> = ({ data }) => {
  ...
}

I want to write a method that accepts such a component and returns a value of the same type as MyComponent.data. This is what I'm struggling with. I'm trying to write this method and invoke it as:

function myFunc<T, U extends BaseProps<T>>(component: React.FC<U>): T {
  return ...
}
const ret1 = myFunc(MyComponent);
const ret2 = myFunc<string, MyProps>(MyComponent);

ret2 has the correct type string but ret comes out to have unknown type. Is there a way I can enforce constraints in the myFunc method such that ret1 is typed correctly without requiring me to explicitly declare types in <>?

Update

Both @futut and @proton have given (almost) identical solution that clearly work in this case! Kudos!

However, the sample code I wrote here was a bit over-simplified. If I change the BaseProps interface to:

interface BaseProps<T> {
  callback: (value: T) => void;
}

Then I can no longer use U["data"]. Is there a way to be able to return a value of the type of value parameter in MyComponent.callback in this case?

PS: Apologies for shifting the goalpost here. My bad at oversimplifying too much.

CodePudding user response:

TypeScript doesn't know that the type U provided can also tell it what T is. You could instead use a single type parameter and use an indexed access to get the type of its data property:

function myFunc<T extends BaseProps<any>>(component: React.FC<T>): T["data"] { 
    /* ... */ 
}

CodePudding user response:

Just explicitly inform typescript, that type of BaseProps.data is going to be function return value, like below:

function myFunc<T, U extends BaseProps<T>>(component: React.FC<U>): U['data'] {
  ...
}

That way you won't have to pass types to generic statement (<>), as long as your component passed to myFunc is typed properly.


Edit: Regarding second version of question: Things get tricky here. You can point to value of a callback by using infer keyword like below:

function myFunc<T extends BaseProps<any>>(
  component: React.FC<T>,
): T['callback'] extends (a: infer R) => void ? R : never {
...

Check documentation regarding that keyword: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html

  • Related