TypeScript with a generic React Component that use generic property
Posted on: 2018-01-09
React is fairly straightforward for simple scenario and TypeScript integrates very well with the JSX syntax (called TSX for TypeScript). However, like many systems, when we push on the limit a little, it starts to be less obvious. One case is the one that you want to have a component generic. Imagine that you want to create a reusable component and want to pass a specific format of information by property and regardless of the implementation you just require to have the object pass to have few fields and nothing else. React per se handle this kind of scenario with the flexibility of JavaScript. Although it's possible with TypeScript, it requires some nimble ability to achieve it.
First of all, let's settle on a basic generic component and desire property.
interface BaseModel {
id: number;
}
interface MyTypeThatExtendBaseModelHere extends BaseModel {
}
interface GenericControlHereProps<T extends BaseModel> {
items: T[];
}
class GenericControlHere<T extends BaseModel> extends React.Component<GenericControlHereProps><T, {}> {
// ...
}
So far, nothing special else that we are using property that is taking a generic list of item which must have an id
field. This is what extend
means. Nevertheless, there is a caveat which is generic when it's time to use the component.
The following code will complain that the property passed is not of the right type. Saying that items
are not assignable to type never[]
.
//...
public render(): JSX.Element {
return <GenericControlHere items={myList} />;
}
//...
The trick is to create a type of component that will explicitly mention the generic type to use.
type ExplicitUseMyType = new () => GenericControlHere<MyTypeThatExtendBaseModelHere>;
const GenericControlHereWithExplicitDefinedProperty = GenericControlHere as ExplicitUseMyType;
//...
public render(): JSX.Element {
return <GenericControlHereWithExplicitDefinedProperty items={myList} >
}
//...
This is a lot of gymnastics to satisfy TypeScript. The reason is that when calling directly the generic component that TypeScript cannot infer what will be the type of the property passed in relation to the component. By creating a new type which passes the generic type it fixes the problem. The component knowns that it of a specific type and the property passed is the same in the render, hence everything compiles.
There is a little syntax detail which is that the type created using new () =>
. This indicates that it's not an object literal that respect the specific structure, but an instance of the type -- it needs to have a parameterless constructor which the React.Component is. The cast is not a pretty strategy but works fine since the type we are casting is having the new ()
mention. When React will render the casted type, it will be able to invoke the constructor and create the component.