Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async-safe, static lifecycle hooks #6

Merged
merged 22 commits into from
Jan 19, 2018
Merged
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9818f57
Initial proposal for async-friendly, static lifecycle hooks
bvaughn Dec 8, 2017
839547f
Added a note about the prefix
bvaughn Dec 8, 2017
b7189cc
Made deprecation/rename clearer
bvaughn Dec 8, 2017
aad6845
Hopefully clarified the computeMemoizeData() placeholder example
bvaughn Dec 8, 2017
99988da
Renamed example method for improved clarity
bvaughn Dec 9, 2017
4a34d25
Added note about aEL being unsafe in cWM
bvaughn Dec 9, 2017
902d4b3
Fixed typo
bvaughn Dec 9, 2017
3df7ce6
Fixing typo
bvaughn Dec 9, 2017
1864c93
Removed Facebook-specific terminology
bvaughn Dec 9, 2017
428758b
Tweaked wording for clarity
bvaughn Dec 9, 2017
cfcb7e8
Reorganization (part 1, more to come)
bvaughn Dec 9, 2017
536084d
Added some more focused examples
bvaughn Dec 9, 2017
a1431a4
Added a comment about calling setState on a possibly unmounted component
bvaughn Dec 9, 2017
3c6132e
Cancel async request on unmount in example
bvaughn Dec 9, 2017
4425dbe
Tweaking examples based on Dan's feedback
bvaughn Dec 9, 2017
67272ce
Renamed deriveStateFromProps to getDerivedStateFromNextProps
bvaughn Dec 13, 2017
c7f6728
Renamed prefetch to optimisticallyPrepareToRender
bvaughn Dec 13, 2017
bb2d246
Added `render` to a list
bvaughn Dec 14, 2017
8618f70
Removed static optimisticallyPrepareToRender() in favor of render()
bvaughn Dec 19, 2017
8f6c20e
Renamed getDerivedStateFromNextProps to getDerivedStateFromProps
bvaughn Jan 18, 2018
e05e317
Updated when getDerivedStateFromProps is called and what its argument…
bvaughn Jan 18, 2018
7042a2a
Renamed unsafe_* to UNSAFE_*
bvaughn Jan 18, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions text/0000-static-lifecycle-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ At a high-level, I propose the following additions/changes to the component API.

```js
class ExampleComponent extends React.Component {
static getDerivedStateFromProps(nextProps, prevProps, prevState) {
// Called before a mounted component receives new props.
static getDerivedStateFromProps(nextProps, prevState) {
// Called after a component is instantiated or before it receives new props.
// Return an object to update state in response to prop changes.
// Return null to indicate no change to state.
}
Expand Down Expand Up @@ -160,14 +160,14 @@ class ExampleComponent extends React.Component {

```js
class ExampleComponent extends React.Component {
state = {
derivedData: computeDerivedState(this.props)
};

static getDerivedStateFromProps(nextProps, prevProps, prevState) {
if (nextProps.someValue !== prevProps.someValue) {
static getDerivedStateFromProps(nextProps, prevState) {
if (
!prevState ||
prevState.someMirroredValue !== nextProps.someValue
) {
return {
derivedData: computeDerivedState(nextProps)
derivedData: computeDerivedState(nextProps),
someMirroredValue: nextProps.someValue
};
}

Expand Down Expand Up @@ -359,13 +359,13 @@ class ExampleComponent extends React.Component {

## New static lifecycle methods

### `static getDerivedStateFromProps(nextProps: Props, prevProps: Props, prevState: Props): PartialState | null`
### `static getDerivedStateFromProps(nextProps: Props, prevState: State | null): PartialState | null`

This method is invoked before a mounted component receives new props. Return an object to update state in response to prop changes. Return null to indicate no change to state.
This method is invoked after a component is constructed. Return an object to initialize component state. Note that the value of `prevState` may be null in this case if the constructor did not initialize `this.state`.

Note that React may call this method even if the props have not changed. If calculating derived data is expensive, compare next and previous props to conditionally handle changes.
This method is also invoked before a mounted component receives new props. Return an object to update state in response to prop changes. Return null to indicate no change to state.

React does not call this method before the intial render/mount and so it is not called during server rendering.
Note that React may call this method even if the props have not changed. If calculating derived data is expensive, compare next and previous props to conditionally handle changes.
Copy link

Choose a reason for hiding this comment

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

@bvaughn The PR for React included
a note that getDerivedStateFromProps doesn't receive previous props. Should this sentence be updated?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The wording here is a bit ambiguous but I think that's okay. Current and previous props can be compared, as shown in this example

Copy link

Choose a reason for hiding this comment

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

Ah, previous props in this case means props that have been synced to the local state and can be access via prevState, right? Alright then, I thought this was left over since I think the prevProps parameter got removed in a later iteration.

Copy link

Choose a reason for hiding this comment

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

So @bvaughn does this mean that, given our previous conversation, I have to "sync" all my props to state in order to compare previous props with the new props? Feels like I'm missing something here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not all props, only ones that are directly used to derive state, and only if it's an expensive computation.

Copy link
Member

@gaearon gaearon Jan 26, 2018

Choose a reason for hiding this comment

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

Does storing "mirrored" props in state in addition to their derivatives not go against the React principle of state as a minimal representation?

It's a bit different though. You're not storing a "mirrored" version, you're storing the previous version. Which is a special case of an accumulation (that happens to completely discard the current value). Accumulation is exactly the purpose of getDerivedStateFromProps and since you already added it (presumably for accumulating something), adding another field to it doesn't hurt.

I guess one could say that a "mirrored" version would also be a special case of accumulation (that discards the previous value) but that's sophism IMO :-)

Choose a reason for hiding this comment

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

since you already added it (presumably for accumulating something), adding another field to it doesn't hurt

Yeah, I see what you mean. I just feel that it muddies the message of "don't put unnecessary stuff in state" where "necessary" no longer precisely means "used in render". Personally I find this a bigger / less intuitive exception to a rule than prevProps's nullability (the reason for which is pretty obvious -- no prevProps on first render). I suspect newer devs would struggle more with the former.

Anyway I see the trade-off, and I appreciate your willingness to explain! 😊

Copy link
Member

Choose a reason for hiding this comment

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

I just feel that it muddies the message of "don't put unnecessary stuff in state" where "necessary" no longer precisely means "used in render".

We might actually revisit this one ^^

With async React it might not be safe to put certain things on the instance fields. See facebook/react#10580.

Copy link

Choose a reason for hiding this comment

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

@bvaughn @gaearon I've tried to flesh out what I mean using a Typescript example - see this gist - to compare the two approaches.

I've extracted this example from a real life component that has more props, but hopefully you get the idea: any component would have to follow a similar pattern.

There are three files in the gist:

  1. the component written as per this proposal
  2. the component written assuming a signature of static getDerivedStateFromProps(nextProps: Props, prevProps: Props, initial: boolean): State
  3. the diff between the two

Notice file 2 is using a slightly different signature to the proposed in my last response; I agree with @gaearon the nullability issue is best avoided, hence prevProps is not nullable and an explicit boolean is provided to indicate whether it's the initial derivation or not.

Does the diff help to show what I mean about the boilerplate required when we have to "sync" to state?

Thanks

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Notice file 2 is using a slightly different signature to the proposed in my last response; I agree with @gaearon the nullability issue is best avoided, hence prevProps is not nullable and an explicit boolean is provided to indicate whether it's the initial derivation or not.

Previous props would still need to be null or undefined in this case, no? Or even more confusingly, they would equal current props.

Anyway~ thanks for the feedback, Paul. I understand and appreciate your concern about verbosity. 🙇

This proposal was discussed in length in December, accepted, and merged into the React repo a couple of weeks ago. I'm going to lock down the thread at this point because I think we've already committed to this API.

Hope you understand!


## Deprecated lifecycle methods

Expand Down