65

Say I have a type like React.ComponentClass<Props> and I want to reference the Props part but it is unnamed in my current context.

To illustrate:

const x = makeComponent(); // typeof x = React.ComponentClass<Something>

In this situation, I can use typeof x but that doesn't give me direct access to the Something type.

What I want is something like this:

type GetPropsType<C extends React.ComponentClass<P>> = P

So that I can extract Something with the type GetPropsType<typeof x>

Anything equivalent would be great.

Bilesh Ganguly
  • 3,792
  • 3
  • 36
  • 58
d.j.sheldrick
  • 1,430
  • 1
  • 11
  • 16
  • Why would you do that? What would you use the type for? You cant assign anything without knowing the type, ie: `const y : GetPropsType = ???` – Tamas Hegedus Jun 30 '17 at 16:53
  • Not 100% sure, but I'd say the only way to do it at runtime is to get an actual object of that type first, and then run typeof on it. In this case, get the props of your react component. – Joel Jun 30 '17 at 22:30
  • Possible duplicate of [Typescript React: Access component property types](https://stackoverflow.com/questions/43230765/typescript-react-access-component-property-types) – Oliver Joseph Ash Mar 15 '18 at 21:52
  • hey have you found some perfect solution? I need to do the same in Vue.js – pranavjindal999 Mar 21 '18 at 12:39

3 Answers3

123

You can use infer:

type TypeWithGeneric<T> = T[]
type extractGeneric<Type> = Type extends TypeWithGeneric<infer X> ? X : never

type extracted = extractGeneric<TypeWithGeneric<number>>
// extracted === number

Playground

d.j.sheldrick
  • 1,430
  • 1
  • 11
  • 16
Xiv
  • 8,862
  • 3
  • 27
  • 30
  • 27
    is there a 'generic' version of this where you don't need to know the actual generic class? or is that impossible? I want to extract the generic type 'generically' ;-) – Simon_Weaver Oct 11 '18 at 04:26
  • 7
    I don't think this is possible right now @Simon_Weaver, there is no way to generically reference a type construct currently, as Typescript does not support Higher Kindred Types. – Xiv Oct 15 '18 at 12:21
  • 5
    Would prever `never` over `null`. – Neonit Apr 02 '19 at 14:59
  • 3
    How would one get the nth generic parameter? – Richard Simões Dec 05 '21 at 01:12
  • 1
    related typescript issue: [Allow classes to be parametric in other parametric classes](https://github.com/microsoft/TypeScript/issues/1213) – TmTron Mar 30 '22 at 12:06
2

You can use pattern matching for that:

namespace React {
    export class ComponentClass<T> {}
}

function doSomethingWithProps<T>(x : React.ComponentClass<T>) : T {
    return null as T;
}

class Something {}
let comp = new React.ComponentClass<Something>();
const instanceOfSomething = doSomethingWithProps(comp);
Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97
  • This works in a way, thanks. it unfortunately means creating a fake constant and then referencing it with `typeof`, so I would have hoped there'd be a better way, but man I'll take what I can get – d.j.sheldrick Jul 01 '17 at 09:18
  • The fake constants are just for demonstration purposes. If you would be so kind and tell us what your end goal is we can provide you a concrete solution. You have fallen into the [xy problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) trap – Tamas Hegedus Jul 01 '17 at 10:33
1

Thought I'd share a real world example to extract the R generic type for MatDialogRef<T, R = any> which is Angular material's dialog box.

I often have components like this, where the output 'model' is a simple interface that isn't quite worthy of its own named type.

export class DialogPleaseWaitComponent implements OnInit {

  constructor(@Inject(MAT_DIALOG_DATA) public data: DialogPleaseWaitModel,
              public dialogRef: MatDialogRef<DialogPleaseWaitModel, { timedOut: boolean }>) { 

  }

So I came up with:

extractMatDialogResponse<T>

I made it work in two 'modes', either taking the component type or the MatDialogRef itself. So I can put extractMatDialogResponse<DialogPleaseWaitComponent> and get back { timedOut: boolean } :-)

Yes - if T is the component type then it requires dialogRef to be the exact name of the property, and it does need to be public.

type extractMatDialogResponse<T = MatDialogRef<any, any> | { dialogRef: MatDialogRef<any, any> }> = 
     T extends MatDialogRef<any, infer R> ? R :                                                                                                          
     T extends { dialogRef: MatDialogRef<any, infer R> } ? R : never;

Also yes this is the same mechanism as used by Xiv, but demonstrates how to make a 'targeted' extractor for a specific use.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689