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

New commit phase lifecycle getSnapshotBeforeUpdate() #33

Merged
merged 15 commits into from
Mar 26, 2018

Conversation

bvaughn
Copy link
Collaborator

@bvaughn bvaughn commented Mar 10, 2018

Copy link

@quantizor quantizor left a comment

Choose a reason for hiding this comment

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

How is this conceptually different from componentWillUpdate and componentWillMount? The similarity in the names makes me wonder if it makes sense to not deprecate the old methods and just reuse them in a semver major change.

@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 10, 2018

How is this conceptually different from componentWillUpdate and componentWillMount?

componentWillUpdate and componentWillMount are called before render, and may be called for a render that is discarded because of a higher priority update. The newly proposed lifecycles would be called during the "commit" phase.

This draft docs page does a nice job in explaining the difference and why it matters to distinguish between the two: https://deploy-preview-587--reactjs.netlify.com/docs/strict-mode.html#detecting-unexpected-side-effects

The similarity in the names makes me wonder if it makes sense to not deprecate the old methods and just reuse them in a semver major change.

Reusing the old lifecycles would not be a good option for a couple of reasons. We currently plan to support the old lifecycle functionality in future releases, with an "UNSAFE_" prefix. Having similarly named methods with significantly different behavior would cause confusion and could make updating from 16 to 17 significantly more difficult.

We will also want these new lifecycles to be available in a minor release of 16- so people can begin upgrading to use async. For this reason as well the names must be unique.

Some familiarity is probably helpful though, since people already have a concept of mounting and updating.

@gaearon
Copy link
Member

gaearon commented Mar 11, 2018

Specifically an important distinction is that people expect to be able to use componentWillMount for setting the initial state, but by the point these lifecycles run it's already too late. So changing what componentWillMount would be too confusing given all the existing code and examples that assume it happens before render (and on the server).

@gaearon
Copy link
Member

gaearon commented Mar 11, 2018

I do wish we could recycle the existing names though since "will mount" and "will update" indeed seem like ideal names for these APIs 😢

@gaearon
Copy link
Member

gaearon commented Mar 11, 2018

I guess we can add them under new names and then eventually find better names for them when/if we ever introduce a new lifecycle API.

@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 11, 2018

I do wish we could recycle the existing names though since "will mount" and "will update" indeed seem like ideal names for these APIs 😢

Yeah ☹️I had the same thought. It's unfortunate that those names are tainted.

I'm definitely open for bike-shedding on better names than the ones I proposed initially though. 😄

@quantizor
Copy link

quantizor commented Mar 12, 2018

I like the idea of componentWillFlush, since that implies a stronger correlation with the "flush to DOM" part of the rendering flow.

Could we even perhaps just have a single event for both cases of mounting and updating? I'm curious if the extra API surface is necessary in practice.

@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 12, 2018

I don't think a single lifecycle is a good idea, because prevProps and prevState would be null the first time it is called. I think the ergonomics of that might be a bit awkward.

@gaearon
Copy link
Member

gaearon commented Mar 12, 2018

Maybe

componentWillFlushMount
componentWillFlushUpdate

@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 12, 2018

I don't object too strongly to that, but I did intentionally avoid using "will" or "did" in the name because of the potential to cause confusion. With the (kind of awkward) caveat of componentWillUnmount, we're training people to be cautions of the "will" lifecycles, so I didn't feel great about adding new caveats.

@dantman dantman mentioned this pull request Mar 12, 2018
@acdlite
Copy link
Member

acdlite commented Mar 13, 2018

Internally, we use the prepare prefix to refer to this phase Nevermind, I misremembered: we use those for the "complete" phase

@trueadm
Copy link
Member

trueadm commented Mar 13, 2018

componentDidRender?

@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 13, 2018

componentDidRender was my first idea, actually 😁but I think it's important to have unique names for the did-mount case and the did-update case.

@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 13, 2018

Renamed componentIsUpdating to getSnapshotBeforeUpdate and dropped the concept of the before-mount lifecycle. Doesn't seem useful for any case I can think of.

@bvaughn bvaughn changed the title [WIP] new "commit" phase lifecycles [WIP] new "commit" phase lifecycle getSnapshotBeforeUpdate() Mar 13, 2018
@bvaughn bvaughn changed the title [WIP] new "commit" phase lifecycle getSnapshotBeforeUpdate() [WIP] new commit phase lifecycle getSnapshotBeforeUpdate() Mar 13, 2018
@TrySound
Copy link
Contributor

Can getSnapshotBeforeUpdate be static? Refs can be kept in state.

@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 14, 2018

Briefly considered making it static but decided against it because of refs. Felt odd to force refs to be stored in state.

@Swizec
Copy link

Swizec commented Mar 14, 2018

What about componentWillCommit? You mention a couple times that this happens during the commit phase before the component commits to the DOM.

@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 14, 2018

I'm personally not a fan of the "componentWill" prefix for this use-case because (with the exception of componentWillUnmount) we're moving away from the other lifecycles with that name. Also, the current proposal is for this method to have a meaningful return value, which is something none of the other "componentWill" and "componentDid" methods do.

@flarnie
Copy link
Collaborator

flarnie commented Mar 14, 2018

One potential use case is in Draft.js, where we have two values we track that are outside of state and get updated roughly at the time this instance method would be called.

The current, short-term work-around we are doing is to update them in the componentDidMount and componentDidUpdate of a child component.

I think this could be a possible solution for the Draft.js use case, @sophiebits would be curious what you think when you are online again.

I also think that for Draft.js we also will not need the 'before-mount' call. Seems like Draft wasn't setting those flags in 'willMount'.

To be more specific about what Draft does by tracking those values - In one case it is intended to block stray 'select' events that fire in IE after Draft has already started a selection update, and in the other case it's caching the latest rendered state, so for both of those I don't think the initial mount is a problem.

@dantman
Copy link

dantman commented Mar 15, 2018

Reparenting (RFC #28) could make use of this feature. <video>/<audio> elements stop playing in some browsers, inputs lose their focus, and scrollable elements lose their scroll position when you move them from one dom node to another dom node.

If these pieces of state are really important to a user, they could be snapshotted before reparenting using this RFC and restored after they are moved to a new dom node.

@sophiebits
Copy link
Member

@flarnie For Draft I don't think we need any new lifecycles, instead maybe just we should refactor our componentDidMount calls so that we don't need to depend on the ordering as strongly. (That likely looks like the parent component controlling the selection handling instead of the leaves.)

Copy link

@ivikash ivikash left a comment

Choose a reason for hiding this comment

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

Should we also mention about shouldComponentUpdate and how will its return value true or false will impact this?


# Motivation

This lifecycle provides a way for asynchronously rendered components to accurately read values from the host envirnment (e.g. the DOM) before it is mutated.
Copy link

Choose a reason for hiding this comment

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

envirnment -> environment


### No return value

The proposed lifecycle will be the first commit phase lifecycle with a meaningful return value and the first lifecyle whose return value is passed as a parameter to another lifecycle. Likewise, the new parameter for `componentDidUpdate` will be the first passed to a lifecycle that isn't some form of `Props` or `State`. This adds some complexity to the API, since it requires a more nuanced understanding the relationship between `getSnapshotBeforeUpdate` and `componentDidUpdate`.
Copy link

Choose a reason for hiding this comment

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

lifecyle -> lifecycle


The proposed lifecycle will be the first commit phase lifecycle with a meaningful return value and the first lifecyle whose return value is passed as a parameter to another lifecycle. Likewise, the new parameter for `componentDidUpdate` will be the first passed to a lifecycle that isn't some form of `Props` or `State`. This adds some complexity to the API, since it requires a more nuanced understanding the relationship between `getSnapshotBeforeUpdate` and `componentDidUpdate`.

An alternative would be to scrap the return value in favor of storing snapshot values on the instance.
Copy link

Choose a reason for hiding this comment

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

favor -> favour

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Both are valid spellings.

@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 19, 2018

Should we also mention about shouldComponentUpdate and how will its return value true or false will impact this?

This lifecycle will be invoked if shouldComponentUpdate doesn't prevent a new render, just like componentDidUpdate. I don't know if this needs to be called out explicitly?

@reactjs reactjs deleted a comment from ivikash Mar 20, 2018
@reactjs reactjs deleted a comment from ivikash Mar 20, 2018
@bvaughn bvaughn changed the title [WIP] new commit phase lifecycle getSnapshotBeforeUpdate() New commit phase lifecycle getSnapshotBeforeUpdate() Mar 20, 2018
@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 22, 2018

This RFC has entered the final comment period.

@bvaughn bvaughn force-pushed the new-commit-phase-lifecycles branch from d08f57f to ec7ce99 Compare March 22, 2018 21:32
@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 23, 2018

I've created a potential polyfill for this method with reactjs/react-lifecycles-compat/pull/1 but I don't think it's worth merging because of the reasons mentioned in the RFC.

@gaearon
Copy link
Member

gaearon commented Mar 23, 2018

Can you expand on what

This would not work in all cases (e.g. Object.defineProperty syntax).

means?

@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 23, 2018

Sure, sure. I meant cases like this:

class Test {
  constructor() {
    Object.defineProperty(this, "componentDidUpdate", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: (prevProps, prevState) => {// ...
      }
    });
  }
}

Which is what Babel would generate for:

class Test {
  componentDidUpdate = (prevProps, prevState) => {
    // ...
  }
}

I could probably have worded this better.

@gaearon
Copy link
Member

gaearon commented Mar 23, 2018

I think I'd be fine with not supporting that pattern. It shouldn't ever be necessary. As long as we document that in the polyfill README.

@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 23, 2018

Gotcha.

It is at least possible to detect and error in this case (when cDU isn't on the prototype) which the polyfill POC PR does.

@gaearon gaearon merged commit dca8f6b into reactjs:master Mar 26, 2018
@bvaughn bvaughn deleted the new-commit-phase-lifecycles branch March 26, 2018 15:19
@bvaughn
Copy link
Collaborator Author

bvaughn commented Mar 26, 2018

Thanks for the suggestions and discussion, everyone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.