6

Let's say I have a class like this:

class PeopleByTag extends React.Component<RouteComponentProps<{ tag: string }>

I need to do something in my constructor, in this example fetch data, but to do that I need to declare a props parameter, but if I don't specify the type it will become any:

constructor(props) {
    super(props); // props is any
    this.loadData();
}

On the other hand, if I redeclare the type the code gets very ugly:

constructor(props: Readonly<{
    children?: React.ReactNode;
}> & Readonly<RouteComponentProps<{
    tag: string;
}, StaticContext, any>>) {
    super(props);
    this.loadData();
}

Is there a way to automatically infer the props type from the class extension while also being able to write a constructor?

I also don't want to use the deprecated lifecycle hooks (i.e. ComponentWillMount).

Ricardo Smania
  • 2,939
  • 2
  • 26
  • 35
  • 1
    Isn't the recommended way to use the `componentDidMount` lifecycle method for fetching data? – sn42 Oct 14 '18 at 12:56
  • Also, calling methods from within the constructor is generally not best practice, as a subclass of the class can override methods but won't have had a chance to do their initialization before you call the method. – T.J. Crowder Oct 14 '18 at 14:38
  • @T.J.Crowder Depends on the case. Not necessarily an issue specifically for a method because prototype method can be overridden by a child. Also, the use of class inheritance is pretty much limited in React, in my experience. – Estus Flask Oct 14 '18 at 14:40
  • @estus - Yes, in React components you're normally better off with aggregation or containment (certainly the React devs think so). But *in general*, it's not a good practice to get in the habit of. – T.J. Crowder Oct 14 '18 at 14:44

2 Answers2

3

Usually constructor itself shouldn't get 'very ugly', just because types can be defined separately as type or interface in case parameter types are verbose.

Constructor props parameter cannot be inferred because React.Component<RouteComponentProps<{ tag: string }>> generic parameter refers to parent class, React.Component, not current class.

As it can be seen in type definitions, this infers proper type for parent constructor, i.e. super.

So this

constructor(props) {
    super(props);
}

is valid. this.props is still properly typed.

In case noImplicitAny compiler option is used, it is:

constructor(props: any) {
    super(props);
}

The use of props typed as any in constructor may result in type mistakes:

constructor(props: any) {
    super(props);

    props.tag; // ok
    props.nonexistentProp; // ok
}

While this.props is type-safe.

A class can be typed with as generic to maintain proper props type in constructor, but this can be considered overkill:

export class PeopleByTag<P extends { tag: string }> extends React.Component<P> {
  constructor(props: Readonly<P>) {
    super(props); // props is any

    props.tag; // ok
    props.nonexistentProp; // not ok
    props.children; // not ok, but we rarely ever need children in constructor
  }
}

It may be beneficial to prevent the use of props in constructor by providing incompatible type for it:

constructor(props: never) {
    super(props);

    props.tag; // not ok
}

If props argument was passed to super, this.props and props are interchangeable in JavaScript. They aren't interchangeable in TypeScript. this.props may be accessed in constructor for properly typed props.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
0

I would recommend defining interfaces or type aliases to reduce the duplication, as estus said. Note however that the type of the props parameter to the constructor can just be Readonly<RouteComponentProps<{ tag: string }>>, since the TypeScript compiler will automatically add the children property based on declarations in @types/react and you can rely on the defaults for the second and third type arguments to RouteComponentProps, just as you did in the extends clause. (Arguably, if the parameter to the constructor is the real props object, then its type should include the children property and omitting the children property is bogus, but the omission is common practice and even appears in the declaration of the React.Component super constructor. I've also omitted the Readonly in one project, but it seems that may no longer be recommendable.) At that point, maybe you'll decide that the type is short enough that repeating it is OK and it's not worth defining an interface or type alias.

By the way, here's the open suggestion for method and constructor parameter types to default to the types from the superclass.

Matt McCutchen
  • 28,856
  • 2
  • 68
  • 75