Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React with TypeScript - Type {...} is not assignable to type 'Readonly<S>'

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!

like image 757
amogower Avatar asked Aug 02 '18 10:08

amogower


1 Answers

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.

like image 66
Titian Cernicova-Dragomir Avatar answered Oct 25 '22 19:10

Titian Cernicova-Dragomir