Reputation: 353
I'm using an abstract class as a parent React component that a number of child components extend in my application.
It has a default state as well as a couple of class methods that can optionally be overwritten from an extending component.
The abstract component looks like this:
import * as React from 'react';
export interface StatefulProps {
observers?: Array<(context: any, state: object) => any>;
}
export interface StatefulState {
machine: {
[propName: string]: any;
};
name: string;
value?: any;
}
export default abstract class StatefulComponent<P extends StatefulProps, S extends StatefulState> extends React.Component<P, S> {
static defaultProps = {
observers: [],
};
state = {
machine: {},
name: '',
};
abstract generateState(stateName: string, stateParam?: any): object;
shouldComponentUpdate(_: P, nextState: S) {
const { name } = this.state;
if (nextState && nextState.name !== name) {
return true;
}
return false;
}
goToState = (stateName: string, stateParam?: any) => {
const { observers } = this.props;
this.setState(
{
name: stateName,
machine: this.generateState(stateName, stateParam),
},
() => {
if (observers && observers.length) {
observers.forEach(observer => {
if (observer instanceof Function) {
observer(this, this.state);
}
});
}
}
);
}
}
Yet state is underlined by tslint which displays the following error on hover:
Property 'state' in type 'StatefulComponent<P, S>' is not assignable to the same property in base type 'Component<P, S, any>'.
Type '{ machine: {}; name: string; }' is not assignable to type 'Readonly<S>'.
I can't figure out for the life of me why it's complaining...
I'm fairly new to TypeScript so it's completely plausible I'm being a total muppet in the way I'm doing things but any advice/help would be much appreciated!
Upvotes: 14
Views: 10306
Reputation: 250396
You can't assign an object literal to something that is of a generic type. The generic type constrain defines the minimal set of properties that S
can have, but it could have more properties, some of them mandatory, consider this derived class:
class MyStatefulComponent extends StatefulComponent<StatefulProps, StatefulState & { newMandatoryProp: number }> {
constructor(p: StatefulProps){
super(p);
this.state.newMandatoryProp // will be undefined beacuse the base class did not initailize it correctly
}
generateState(stateName: string, stateParam?: any): object { return null as any }
}
You can use a type assertion to do this anyway but it is not type safe:
export default abstract class StatefulComponent<P extends StatefulProps, S extends StatefulState> extends React.Component<P, S> {
constructor(props: P) {
super(props);
this.state = {
machine: {},
name: '',
} as S;
}
}
You could also have an abstract method for creating the state, and pass responsibility of creating the full state object to the derived class.
Upvotes: 7