42

When I do a search on this issue, I can only find questions that modify this.state directly somewhere in a method body instead of using this.setState(). My problem is that I want to set a starting state in the constructor as follows:

export default class Square extends React.Component<any, any> {
  constructor(props: any) {
    super(props);
    this.state = {
      active: false
    };
  }

  public render() {
    ...
  }
}

The app fails to start with the following compile error:

Cannot assign to 'state' because it is a constant or a read-only property

And this is because in definition of React.Component we have:

readonly state: null | Readonly<S>;

So I'm not sure how to go about this. The official react tutorial in JS directly assigns to this.state and says that it is an acceptable pattern to do so in the constructor, but I can't figure out how to do this with TypeScript.

jeanluc
  • 1,608
  • 1
  • 14
  • 28

3 Answers3

43

Before rolling back (as suggested by @torvin's answer), please read through https://github.com/DefinitelyTyped/DefinitelyTyped/pull/26813#issuecomment-400795486.

This was not meant to be a regression - the solution is to use state as a property. It is better than previous approach (setting state in a constructor) because:

  • you don't need constructor at all anymore
  • you can't forget to initialize state (it is now a compile-time error)

For example:

type Props {}

type State {
  active: boolean
}

export default class Square extends React.Component<Props, State> {
  public readonly state: State = {
    active: false
  }

  public render() {
    //...
  }
}

Another approach:

type Props {}

const InitialState = {
  active: false
}

type State = typeof InitialState

export default class Square extends React.Component<Props, State> {
  public readonly state = InitialState

  public render() {
    //...
  }
}
Nikola Mihajlović
  • 2,286
  • 2
  • 19
  • 23
  • 30
    Better or not, it's kind of annoying when they suddenly introduce breaking changes on minor version upgrades – Burak Jun 28 '18 at 08:35
  • 5
    Just wanted to note that you could use 'state as a property' pattern before this change. What this change does - it forces you to use it. – torvin Jun 28 '18 at 09:28
  • 1
    I'm tempted to mark this as the accepted answer since Nikola is right, this is not a bug or regression, so the solution should not be rolling back but rather conforming to the new standard. @torvin, perhaps you should edit your answer to include this option as well? I'll wait for feedback before taking any action – jeanluc Jun 28 '18 at 17:24
  • @jeanluc both approaches are fine, but I think having them as separate answers is more in lines with SO rules. I like this approach too, but it has downsides (see [this comment about forgetting `readonly` in derived classes](https://github.com/Microsoft/TypeScript/issues/25134#issuecomment-400885742)). Also, please note that the `readonly` modifier has been removed again in the following [pull request](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/26912) and will soon be pushed to npm. – torvin Jun 28 '18 at 22:27
  • @torvin But when you assign as a property, it doesn’t apply type checks to what you assign to it. With the constructor approach, it’s an error to assign the incorrect state type and you only have to write the state type once in your `extends React.Component<{}, TState>`. – binki Jun 29 '18 at 02:39
  • @torvin and, as others in the GitHub issue comment, defining state youreslf sidesteps the type definition’s `ReadOnly<>`… – binki Jun 29 '18 at 02:44
  • 1
    It appears that this change will be reversed in another update to `@types/react`, see this [pull request](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/26946) for details. For the moment, the simpler course may be the follow @torvin's suggestion and roll back. – EFC Jun 29 '18 at 14:54
  • https://stackoverflow.com/questions/52167263/why-my-object-from-import-let-is-readonly-react-js?noredirect=1#comment91288499_52167263 Any suggestion for this – Nikola Lukic Sep 05 '18 at 08:43
  • @NikolaLukic, I have a base class that has a few common generic types in state, the extended class pass default values of the generic type through constructor `super(...)`. So I need to initialize the state within the base class constructor. there is any other way than downgrading `@types/react` version? – Saeed Zhiany Apr 24 '19 at 05:35
35

This appears to be a recent change in @types/react introduced in commit 542f3c0 which doesn't work very well, considering the fact that Typescript doesn't support assigning parent's readonly fields in derived constructors.

I suggest rolling back to a previous version of @types/react. Version 16.4.2 appears to be the last one before the unfortunate change has been made.

You can pin the version by removing the ^ in your package.json:

"devDependencies": {
  ...
  "@types/react": "16.4.2",

Also check out the discussion about this change on DefinitelyTyped github pull request page

torvin
  • 6,515
  • 1
  • 37
  • 52
-2

Because state is a readonly property of the component, it can't be set field-by-field, but you can still do:

constructor(props: MyProps) {
  super(props);
  this.state = {
    // include properties here
  }
}
Michael Landis
  • 1,074
  • 7
  • 12