0
votes

I want to make a function which will return typeof React Component, which must implement a specific props interface.

I want to return a type, not an instance of that type.

So given this:

interface INameProps {
    name: string;
}
interface ITypeProps {
    type: string;
}
class Component1 extends React.Component<INameProps> {}
class Component2 extends React.Component<INameProps> {}
class Component3 extends React.Component<ITypeProps> {}

I would like to make a function, that could return components, for which the props interface extends INameProps

So far I figured this out:

export function getComponents<T extends INameProps, S extends any>(): React.Component<T, S> {
    return Component1; // should be fine
    return Component2; // should be fine
    return Component3; // should not be allowed, since T for Component3 does not extend INameProps
}

But this is not correct - return type of this function is an instance of those components.

To get a type, I would think I would just have to add typeof keyword like so:

export function getComponents<T extends INameProps, S extends any>(): typeof React.Component<T, S>

But TypeScript does not like, that I add generics <T, S> after React.Component.

It compiles, when I define it like this:

export function getComponents<T extends INameProps, S extends any>(): typeof React.Component

But this does not do what I want - return type of function like this is a type of any React.Component.

How do I write this?

EDIT:

I went looking around, and found React.ComponentType (For Flow, I didn't see any documentation for TypeScript tho)

Turns out, the answer is rather simple. I was trying to come up with my own way using advanced types of TypeScript, but React already thought of this -

export function getComponent(): React.ComponentType<INameProps> {
    return Component1; // allowed
    return Component2; // allowed
    return Component3; // not allowed, since props for Component3 does not extend INameProps
}
2
return Component1 gives you the constructor function, not an instance, it's the closest you get to the type of an instance. TypeScript types are compile-time only and not something you can pass around like a value at run-time. I think what you have is probably what you want. You should be able to e.g. <ReturnedComponent/> in a tsx to actually instantiate the component.dezfowler

2 Answers

2
votes

A constructor (or type) of some class C can be expressed as new () => C, plus-minus constructor args and generic types.

Still, your approach is not going to work here. When you have a generic function then it's the caller's decision to select its generic types. Sometimes they are inferred by the compiler, but anyway it's the caller that controls them. So someone might call your function as getComponent<SomeIrrelevantType>, and what will you want to return then? In runtime you are not even able to see that the generic type was set to something irrelevant.

The approach you can use instead is something similar to this:

export function getNamePropsComponents():
    (new () => React.Component<INameProps, any>)[] {
  return [Component1, Component2]; // should be fine
  return [Component3]; // doesn't compile
}
0
votes

I went looking around, and found React.ComponentType (For Flow, I didn't see any documentation for TypeScript tho)

Turns out, the answer is rather simple. I was trying to come up with my own way using advanced types of TypeScript, but React already thought of this -

export function getComponent(): React.ComponentType<INameProps> {
    return Component1; // allowed
    return Component2; // allowed
    return Component3; // not allowed, since props for Component3 does not extend INameProps
}