Skip to content

[react] Allow to initialize state in constructor #26912

Merged
johnnyreilly merged 1 commit intoDefinitelyTyped:masterfrom
magnetnation:patch-1
Jun 28, 2018
Merged

[react] Allow to initialize state in constructor #26912
johnnyreilly merged 1 commit intoDefinitelyTyped:masterfrom
magnetnation:patch-1

Conversation

@magnetnation
Copy link
Copy Markdown

Partial rollback of the #26813 that allow developers to follow react recommended way of initializing state in constructor

Partial rollback of the #26813 that allow developers to follow react recommended way of initializing state in constructor
@Hotell
Copy link
Copy Markdown
Contributor

Hotell commented Jun 28, 2018

you didn't read what I've writen did you ? :) Please try again #26813 (comment)

TL;DR:

This change is no rollback at all :) If you wanna rollback, you need to remove union with null as well.

@magnetnation
Copy link
Copy Markdown
Author

You should not be rude - it will not help.
I propose to rollback the main reason of all controversy. Union with null is a valid change that correspond to the way react works.
P.S.
Usually the developer that breaks code is responsible for rollback. You are free to follow this advise if your wish.

Comment thread types/react/index.d.ts
// In the future, if we can define its call signature conditionally
// on the existence of `children` in `P`, then we should remove this.
readonly props: Readonly<{ children?: ReactNode }> & Readonly<P>;
readonly state: null | Readonly<S>;
Copy link
Copy Markdown
Contributor

@Hotell Hotell Jun 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a rollback, you need to remove null from the definition as well

otherwise there will be still errors:

image

this will still give you might be null errors when you're gonna read the state, which is again, idiomatic typescript - properties need to be defined on class if you gonna use them in advance, or to be more precise -> TS is using state definition from base class as it was not overridden in my component

@Hotell
Copy link
Copy Markdown
Contributor

Hotell commented Jun 28, 2018

I'm not rude mister :) just annoyed to repeat again things that I've explicitly described and pointed to.

Please remove the null as explained. And you're speaking against yourself, that null is a valid change, it's not as it will introduce "breaking change" as well

@typescript-bot typescript-bot added the Popular package This PR affects a popular package (as counted by NPM download counts). label Jun 28, 2018
@typescript-bot
Copy link
Copy Markdown
Contributor

@magnetnation Thank you for submitting this PR!

🔔 @johnnyreilly @bbenezech @pzavolinsky @digiguru @ericanderson @morcerf @tkrotoff @DovydasNavickas @onigoetz @theruther4d @guilhermehubner @ferdaber @jrakotoharisoa - please review this PR in the next few days. Be sure to explicitly select Approve or Request Changes in the GitHub UI so I know what's going on.

If no reviewer appears after a week, a DefinitelyTyped maintainer will review the PR instead.

@typescript-bot typescript-bot added the Revision needed This PR needs code changes before it can be merged. label Jun 28, 2018
@typescript-bot
Copy link
Copy Markdown
Contributor

@magnetnation One or more reviewers has requested changes. Please address their comments. I'll be back once they sign off or you've pushed new commits or comments. Thank you!

@magnetnation
Copy link
Copy Markdown
Author

Maybe I missing something but react state default value is null if not initialized and therefore the declaration of state should look like:

state: null | Readonly<S>;

Exactly the way you did it in the original commit. So I suggest to leave it.

@magnetnation
Copy link
Copy Markdown
Author

magnetnation commented Jun 28, 2018

Regarding the errors that might arise from the union with null they are on the mercy of each development team and can be switched on/off using strictNullChecks flag of tsc.

@Hotell
Copy link
Copy Markdown
Contributor

Hotell commented Jun 28, 2018

Regarding the errors that might arise from the union with null they are on the mercy of each development team and can be switched on/off using strictNullChecks flag of tsc.

yeah that's right ✌️ thanks again and sorry for casting "rude" behaviour on you, that wasn't my intention at all

@johnnyreilly johnnyreilly merged commit 14a76fc into DefinitelyTyped:master Jun 28, 2018
@magnetnation magnetnation deleted the patch-1 branch June 28, 2018 12:10
@Ky6uk
Copy link
Copy Markdown
Contributor

Ky6uk commented Jun 28, 2018

Are there plans to push a new package with this fix?

@milesj
Copy link
Copy Markdown
Contributor

milesj commented Jun 28, 2018

Having this null union is a giant pain in our codebase right now...

@johnnyreilly
Copy link
Copy Markdown
Member

@milesj feel free to submit a PR to improve things! Being able to initialise state in the constructor is a must but otherwise go forth and conquer!

Comment thread types/react/index.d.ts
// on the existence of `children` in `P`, then we should remove this.
readonly props: Readonly<{ children?: ReactNode }> & Readonly<P>;
readonly state: null | Readonly<S>;
state: null | Readonly<S>;
Copy link
Copy Markdown
Member

@Jessidhia Jessidhia Jun 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am too late in making this comment, but I don't see why removing this readonly is at all a fix. According to TypeScript rules, readonly does not affect whether a property is writable in the constructor.

The readonly should be here to guard against assigning to state outside the constructor, which is invalid according to both TypeScript and React rules.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, ideally, it would be nice to be able to use conditional types to make state abstract if the type of S is not empty; this would probably fix all the problems with state being left accidentally uninitialized without adding this null to the type. The default also should be null, and not {}, but apparently that breaks some of the mapped types that depend on S.

Copy link
Copy Markdown
Author

@magnetnation magnetnation Jun 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are partially correct. Readonly properties are writable in the constructors of the the class that defines the property. Not in subclasses. But it is completely valid and recommended to do so in React. See microsoft/TypeScript#25134 (comment) for more details.

I would like to emphasize that union with null is the correct way to go as react state is null unless initialized. Therefore this typescript construction describes it in the best possible way.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what @magnetnation said.

The readonly should be here to guard against assigning to state outside the constructor, which is invalid according to both TypeScript and React rules.

That wont help, when you'll define state on your class via class property, it will override Base class readonly state and will left you just with state so you need to provide the readonly explicitly ( this might be issue on TS level )

readonly state from base class ( correct errors, but wrong runtime as no state has been defined )
image

readonly overridden:
image

The default also should be null, and not {}

yeah that's right, also props should be object not {} etc, which would introduce another breaking changes :)

@shincysl
Copy link
Copy Markdown

shincysl commented Jun 29, 2018

having the null breaks react-redux connect(). please don't do this in a patch update.

@Hotell
Copy link
Copy Markdown
Contributor

Hotell commented Jun 29, 2018

hmm so I double checked again today, and this won't fix introduced breaking change so we have to rollback completely. But I told you so! @magnetnation :D #26912 (comment) :(

going to submit revert PR asap.

sorry everyone for trouble that this caused.

We should introduce these changes with BC version after

one Pro tip though:
if something breaks, you can always rollback your version of particular package without giving middle fingers and hating on person responsible for that. That won't help anyone and might discourage person who is investing his time for free to make better oss. We are all in this together, so please try to be nice and constructive. Peace ✌️

@magnetnation
Copy link
Copy Markdown
Author

I might be not so bad idea.

I would like to underline one thing. The goal of typescript definitions is not to enforce best practices or make it easier to use it in other libraries. Instead it is to describe underlying react behavior as exact as possible (same idea here).

Lets not forget this.

And if you are going to rollback all changes - please do not forget to reintroduce state: null | Readonly<S> with corresponding version bump.

@Hotell
Copy link
Copy Markdown
Contributor

Hotell commented Jun 29, 2018

@magnetnation

please do not forget to reintroduce state: null | Readonly<S> with corresponding version bump.

I think we cannot do that, as it would "prevent" users to use "react documentation way" to set state from within constructor. Hmm actually it would be still possible but they would need to define the class property type - as they should anyway - idiomatic Typescript.

@milesj
Copy link
Copy Markdown
Contributor

milesj commented Jun 29, 2018

There's also other ways to set state during the constructor phase, that isn't written in the constructor explicitly. A few contrived examples:

A helper method.

class Foo extends React.Component {
  constructor(props) {
    super(props);

    this.setupInitialState();
  }

  setupInitialState() {
    // Lots of logic
    this.state = state;
  }
}

Or state being derived via event/subscription/etc. FinalForm works this way.

class Foo extends React.Component {
  constructor(props) {
    super(props);

    this.api = new API(this.handleState);
  }

  handleState(state) {
    // Lots of logic
    this.state = state;
  }
}

Not sure if TS is smart enough to catch these.

@Hotell
Copy link
Copy Markdown
Contributor

Hotell commented Jun 29, 2018

A helper method.

best practice - you should prefer extracting initializers to pure functions

Or state being derived via event/subscription/etc. FinalForm works this way.

that's possible, but you have to define state upfront

class Foo extends React.Component {
+  state: State
  constructor(props) {
    super(props);

    this.api = new API(this.handleState);
  }

  handleState(state) {
    // Lots of logic
    this.state = state;
  }
}

Anyways

good/best practice is to always initialize state ( same like with redux pattern )

@milesj
Copy link
Copy Markdown
Contributor

milesj commented Jun 29, 2018

Setting the state type property requires you to define every field in the state, with a default value, which you're already doing in the helper method. You're basically duplicating your code, and increasing your file size, especially if there's a lot of fields in your state.

@milesj
Copy link
Copy Markdown
Contributor

milesj commented Jun 29, 2018

Also, has the readonly/null changes been verified with getDerivedStateFromProps? I didn't see a test case for it.

@Hotell
Copy link
Copy Markdown
Contributor

Hotell commented Jun 29, 2018

You're basically duplicating your code, and increasing your file size, especially if there's a lot of fields in your state.

What are you talking about ? you'll have one pure function const initializeState = (props:Props):State => {...}

Also, has the readonly/null changes been verified with getDerivedStateFromProps? I didn't see a test case for it.

instance definitions have no effect on static properties/methods ✌️

@milesj
Copy link
Copy Markdown
Contributor

milesj commented Jun 29, 2018

It was in reference to the 2nd example. You can't state: State without setting the property value.

JulianG added a commit to JulianG/bananatabs that referenced this pull request Jun 30, 2018
JulianG added a commit to JulianG/react-list-drag-and-drop that referenced this pull request Jul 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Popular package This PR affects a popular package (as counted by NPM download counts). Revision needed This PR needs code changes before it can be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants