Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

async-safe, static lifecycle hooks#6

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
acdlite merged 22 commits intoreactjs:masterfrombvaughn:static-lifecycle-methods
Jan 19, 2018

Conversation

@bvaughn
Copy link
Collaborator

@bvaughnbvaughn commentedDec 8, 2017
edited by threepointone
Loading

Note for React Native users: if you see a warning pointing to this page, this was most likely due to a mistake in our release process. This warningwasn’t supposed to fire until we finish writing the documentation for it, and wasn’t supposed to link to this page. We will either publish the documentation ASAP or revert this mistake in a patch release today (7th of March). We’re sorry for the disturbance.


If you reached this page from a warning saying "Legacy Context API has been detected within a strict mode tree.", you should visitthis page for details on the stable Context API.


Replace error-prone render phase lifecycle hooks with static methods to make it easier to write async-compatible React components. Provide a clear migration path for legacy components to become async-ready.

Note that I plan to submit a related RFC soon for a new server-side lifecycle,componentDidServerRender, to offset any server-rendering functionality lost by deprecatingcomponentWillMount.

Please leave comments below for high-level topics. For feedback on specific parts of this proposal, please leave inline comments (on the markdown file).

View proposal with formatting

apostolos, s-mark-holmes-ii, TrySound, jxom, dsblv, radekmie, nhducit, eranimo, Haroenv, flarnie, and 5 more reacted with thumbs up emojiForsakenHarmony and streamich reacted with thumbs down emojiTrySound, jxom, nhducit, lo1tuma, eranimo, rickhanlonii, and jyash97 reacted with hooray emoji
@bvaughnbvaughn changed the titleInitial proposal for async-friendly, static lifecycle hooksasync-safe, static lifecycle hooksDec 8, 2017
@bvaughnbvaughnforce-pushed thestatic-lifecycle-methods branch from8c27453 to839547fCompareDecember 8, 2017 22:32
@abritinthebay
Copy link

As the RFC stands it seems written towards solving problems for one, very specific, workflow.

Maybe that’s just the examples.

The resulting interface though seems extremely verbose & complicated comparatively to the one it replaces.

I like the goal of this though. The idea is good.

OEvgeny reacted with thumbs up emoji

@bvaughn
Copy link
CollaboratorAuthor

bvaughn commentedDec 8, 2017
edited
Loading

Thanks for the input!

As the RFC stands it seems written towards solving problems for one, very specific, workflow.

The goal is to solve all workflows/use-cases in a way that's safer by default (or maybe, in a way that has fewer potential pitfalls, or requires less advanced knowledge to use correctly). If you know of use-cases that it couldn't handle, this is a great time to discuss them!

The resulting interface though seems extremely verbose & complicated comparatively to the one it replaces.

Does the proposed API actually increase verbosity or complexity? I think it just moves things around, but maybe I'm stuck looking at it with a stale perspective.

@trueadm
Copy link
Member

trueadm commentedDec 8, 2017
edited
Loading

I feel this proposal has great benefits over what we have now, but ultimately should probably be merged into an entirely new component API that doesn't involve classes at all. Furthermore, to unify with our other efforts, i.e. compilation of React components, by adding more to already complicated class component will makes things even more complicated to handle.

In my opinion we need a new component API that offers:

  • the same flexibility as before, but with some sensible constraints
  • an async-first approach to dealing with lifecycle events like outlined in this proposal
  • a compiler friendly approach to dealing with things like component folding, bytecode interpretation and dead-code elimination
kentcdodds, bguzryanto, vcarl, j-f1, Jessidhia, wa-Nadoo, and qddegtya reacted with thumbs up emoji

@bvaughn
Copy link
CollaboratorAuthor

Thanks Dom.

I feel this proposal has great benefits over what we have now, but ultimately should probably be merged into an entirely new component API that doesn't involve classes at all.

I'm not sure what the right answer is, but the advantage of this proposal over what you're describing is that it provides anincremental path for existing components to take advantage of async. A completely new, non-class-based API seems both harder to designand much larger effort to adopt.

That being said, I recognize that I can't appreciate the compiler difficulties presented by the class component API like you can.

kentcdodds, vcarl, and TheForgotten69 reacted with thumbs up emoji


This method is invoked before`render` for both the initial render and all subsequent updates. It is not called during server rendering.

The purpose of this method is to initiate asynchronous request(s) as early as possible in a component's rendering lifecycle. Such requests will not block`render`. They can be used to pre-prime a cache that is later used in`componentDidMount`/`componentDidUpdate` to trigger a state update.
Copy link
CollaboratorAuthor

@bvaughnbvaughnDec 8, 2017
edited
Loading

Choose a reason for hiding this comment

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

Inspiration for this method comes fromfacebook/react#7671 (comment):

You would rely on an external system to keep its cache around. Like Relay or some Flux store. So when you request it again, it's available. Just like when you<link rel="prefetch" /> something you don't get a direct handle on the resource. A separate request gets the data.

@sebmarkbage
Copy link
Collaborator

The goal of this proposal is not to be the final "React.next" API. It's to provide a reasonable upgrade path for a large set of components e.g. in a codemoddable way.

The assumption is that:

  1. Whatever the next API change is will be significantly different enough that you can't just codemod your way there. E.g. leaking an instance alone would bail.
  2. It is a relative easy manual change to move to these new life-cycles.
  3. There are significantly fewer components that use these particular life-cycles than there are total components.

It is also much easier to adopt full async in a large codebase this way and a small ever growing portion being compiler compatible with the new API.

Now whether it is worth doing both steps is arguable but that's the idea.

bvaughn, kentcdodds, and vcarl reacted with thumbs up emoji

@trueadm
Copy link
Member

trueadm commentedDec 8, 2017
edited
Loading

Let me take a step back and state what I feel the ultimate goal would be for me: in the future, we don't have class components anymore and state/lifecycles should instead be handled by another mechanism that doesn't force us to break out a declarative render phase into an imperative OO class phase.

That sounds bonkers, but will likely be a future RFC from me once I have time to properly explain it. That being said, incrementally, I feel we shouldstart adding constraints to components:

  • Seal the class instance after construction, so instance variables are no longer possible
  • Come up with a new lifecycle interface (like outlined in this PR) that replaces the current lifecycle methods
TheForgotten69, gavrilyak, and sejoker reacted with thumbs up emoji

@markerikson
Copy link

markerikson commentedDec 8, 2017
edited
Loading

@trueadm : If instance variables are no longer possible, and lifecycle methods are going away, how would React components handle non-React interop? The standard example would be:

classMyPluginWrapperextendsComponent{componentDidMount(){this.$theDiv=$(this.theDiv)this.$theDiv.somePlugin("create");}componentDidUpdate(){this.$theDiv.somePlugin("update",this.props.someValue);}componentWillUnmount(){this.$theDiv.somePlugin("destroy");}render(){return<divref={theDiv=>this.theDiv=theDiv}/>;}}

In this use case, references to non-React values are stored as instance fields, and created/updated/destroyed in lifecycle methods.

suchipi, vcarl, oriSomething, appsforartists, mijay, arackaf, and streamich reacted with thumbs up emoji

@aickin
Copy link

aickin commentedDec 8, 2017
edited
Loading

Thanks for your work on this RFC!

I may just not have enough background in the async discussion (is there a place where those ideas are laid out?), but I have to say that I found this RFC very difficult to understand. A few suggestions that probably would have made it easier for me and maybe might make it easier for others:

  1. Move the motivation/description of problem to the top. The code sample is long, and was somewhat hard to grok when I had no idea what was being changed or why.
  2. I'd suggest beefing up the description of the three problems you listed in Common Problems. It sounds like some of these are programmer errors; why aren't they currently causing problems? Or are they?
  3. Consider separating the code sample out into multiple examples, ideally each connected to a Common Problem. For me, it was very difficult to keep all the things going on in those lifecycle method in my head at once.
  4. It seems like there are going to be changes to the order of lifecycle methods in an async version, and this RFC assumes the reader understands what those are. (I might be wrong here; it just seemed implied by some of the text.) If so, explaining that or linking to a clear explanation would be great!

Again, thanks for thinking through these issues and for all your work. Also, I apologize for the stuff I just have totally misunderstood. I'm excited to see async moving forward!

flarnie reacted with thumbs up emoji

@trueadm
Copy link
Member

trueadm commentedDec 8, 2017
edited
Loading

Note: this isn't really that relevant to this PR proposal.

@markerikson I'm glad you asked :) lately I've been using a lot of ReasonReact and the place for refs is most definitely state, which would fit nicely into mycreateRef proposal:facebook/react#11555.

For more information on how ReasonReact deals with refs:https://reasonml.github.io/reason-react/docs/en/react-ref.html

@bvaughn
Copy link
CollaboratorAuthor

bvaughn commentedDec 8, 2017
edited
Loading

Move the motivation/description of problem to the top.

That's useful feedback for the RFC process in general. This proposal just follows thestandard template.

It seems like there are going to be changes to the order of lifecycle methods in an async version, and this RFC assumes the reader understands what those are.

I don't think I meant to imply any change in ordering. Could you clarify where you thought this?

Actually@aickin, do you mind splitting up your comments and adding them inline? Large, multi-bullet comments are hard to respond to in a single thread like this. 😄

aickin reacted with thumbs up emoji

@aickin
Copy link

aickin commentedDec 8, 2017
edited
Loading

I don't think I meant to imply any change in ordering. Could you clarify where you thought this?

I think it was a vague impression I got from the following ideas:

  1. The async experiment you explained showed non-idempotent function calls in functions likecomponentWillMount, and you mentioned it's a problem that they could get called "multiple times".
  2. Those types of non-idempotent function calls should move to the new static functions, which will solve the problem.

That implied to me that the new static functions are called in a different way than their previous instance method analogues, which made me think that there are more changes to lifecycle order/number of calls. Honestly, though, it was a very rough impression that I may have just totally misread.

@aickin, do you mind splitting up your comments and adding them inline? Large, multi-bullet comments are hard to respond to in a single thread like this.

Sure, sorry!

ETA: I moved my big list of nits out from this discussion into line comments in the document. I hope they are genuinely helpful, and please let me know if they are not!


addExternalEventListeners();

this._computeMemoizeData(nextProps);
Copy link

@aickinaickinDec 8, 2017
edited
Loading

Choose a reason for hiding this comment

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

IsnextProps defined here? I may just be missing it, but I don't see where it would be defined.

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

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

Whoops. This was a typo. Will fix.

this.setState({ externalData })
);

addExternalEventListeners();

Choose a reason for hiding this comment

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

Adding event listeners in componentWillMount is a bug, right? I know that it would make SSR fail (since there's no DOM), and it seems like you say it's a problem in the Common Problems section. If that's right, a comment to that effect would be super helpful!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Flux stores are external event listeners that don't need a DOM, but also SSR alone is not necessarily that common. Especially for long tail.

Choose a reason for hiding this comment

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

Thanks, that's helpful context, but my question here is whether or not this line is currently considered a bug. It sounds like this document is saying yes, it is, although I'm not entirely sure. If so, I think a comment would help understanding. Does that make sense?

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yeah. This is not a good pattern. (I listed it below as a common problem we see.) I show it here only to show where itshould be done instead. I've added an inline comment to this part of the example though to make it clear that it's not a pattern we recommend. 😄


###`static prefetch(props: Props, state: State): void`

This method is invoked before`render` for both the initial render and all subsequent updates. It is not called during server rendering.

Choose a reason for hiding this comment

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

IIRC componentWillMount is called in SSR. Since componentWillMount is being deprecated and replaced with prefetch and prefetch is not called in server rendering, that means that there would be no lifecycle calls on the server any more, right? Have you looked into what use cases cWM is used for on the server?

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

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

This is what my note in the PR description is about. I'll be adding a related RFC shortly withcomponentDidServerRender based on the discussionshere andhere. 😄

aickin reacted with thumbs up emoji
Copy link

@aickinaickinDec 9, 2017
edited
Loading

Choose a reason for hiding this comment

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

<facepalm emoji> I just totally missed that line. Sorry!

// (Async request won't complete before render anyway.)
// If you only need to pre-fetch before mount,
// You can conditionally fetch based on state.
asyncLoadData(props.someId);

Choose a reason for hiding this comment

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

The example forprefetch was a little confusing to me, since it calls a method that elsewhere in the example returns a Promise of data, butprefetch then ignores that Promise. It makes sense if you understand thatasyncLoadData caches the response for the next call, but that's not really obvious. Perhaps rename that methodasyncCacheData orprecacheData?

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

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

The name is a reference to linkprefetch in the browser world, since the functionality is kind of analogous. It's good feedback though. We'll see if others find the name confusing.


Avoid introducing any side-effects, mutations, or subscriptions in this method. For those use cases, use`componentDidMount`/`componentDidUpdate` instead.

###`static deriveStateFromProps(props: Props, state: State, prevProps: Props): PartialState | null`

Choose a reason for hiding this comment

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

Is there a reason for switching from passing innextProps incomponentWillReceiveProps to usingprevProps inderiveStateFromProps? It may make updating code easier to keep the same semantics and names.

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

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

You can name the parameternextProps if you'd prefer 😄 It's the same thing.

2. Find and fix async bugs before they impact end-users by intentionally triggering them in a deterministic way.
3. Gain confidence that our existing products could work in async.

I believe this GK confirmed what we suspected about the legacy component API:_It has too many potential pitfalls to be safely used for async rendering._

Choose a reason for hiding this comment

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

"I believe this GK confirmed ..." was a little confusing for me as a non-Facebooker. I assume it's referring to the experiment y'all did, but took me a few beats to guess that.

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah, thank you. I'll change this to use non-Facebook-specific terminology.

aickin reacted with thumbs up emoji
@markerikson
Copy link

@trueadm : so just to clarify, the place to storeanything a component needs to reference would now be in "state", even if those values aren't directly related to re-rendering? The rule of thumb so far has been "if it relates to rendering, put it inthis.state, otherwise store it asthis.someValue".

suchipi and guillaume86 reacted with thumbs up emoji


This method is invoked before`render` for both the initial render and all subsequent updates. It is not called during server rendering.

The purpose of this method is to initiate asynchronous request(s) as early as possible in a component's rendering lifecycle. Such requests will not block`render`. They can be used to pre-prime a cache that is later used in`componentDidMount`/`componentDidUpdate` to trigger a state update.

Choose a reason for hiding this comment

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

It may be just me, but I find theprefetch-componentDidMount two-step a bit unwieldy as a component developer; it requires me to implement some caching layer and ensure I don't end up calling the data load twice, which leads to very confusing control flow. It might be nicer to let prefetch return a Promise, which would be passed in tocomponentDidMount/Update, though I haven't thought that through very well.

Copy link
Collaborator

@sebmarkbagesebmarkbageDec 9, 2017
edited
Loading

Choose a reason for hiding this comment

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

That seems reasonable but the whole prefetching thing is also really a micro-optimization. It's a power-feature. You can just do it incomponentDidMount too.

I think this touches on a bigger issue that it is hard to pass values between different life-cycles which needs a bigger API refactor.

That said, I think it is better that it is awkward because in an async scenario you can be interrupted beforecomponentDidMount and then get rerendered. In which case you would get anotherprefetch call. You wouldn't want such prefetches to trigger new fetches neither.

Same thing for just any random rerender for any other reason since this will also be in the update path.

aickin and bvaughn reacted with thumbs up emoji

Choose a reason for hiding this comment

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

Could there be a method, sayasync load(props, state) (orfetch) that is used for both prefetching and actually updating the state like withcomponentDidMount() in the example? What would happen is that it returns a Promise resolving to key/value changes to be made to the state. (To make it more convenient, it could actually be an array of state changes, to make it easily used withPromise.all())

This would also solve the issue of the unmounting component attempting tosetState(). In the case of an unmounted component, once the Promise fromload() resolves, its result is just ignored.

markerikson reacted with thumbs up emoji
Copy link
CollaboratorAuthor

@bvaughnbvaughnDec 10, 2017
edited
Loading

Choose a reason for hiding this comment

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

Sounds similar to what@philraj proposedhere?

I mentioned that I think it's also somewhat common for async requests to do things like dispatch Flux store actions on completion (rather than updatingstate). I think it's also common to make multiple async request,and for a single request to update multiple state keys. (I'm not commenting on the merits of these design patterns, just saying that I've observed them.)

I find myself hesitant about the idea of baking handling of this async promise-resolution into React core for some reason. I can't put my finger on why exactly and so I don't trust my opinion yet. (Maybe I worry about potential complexity or confusion. Maybe the idea is just too new to me.)

Edit Whoops. Looks like you already saw and responded to that thread. (I'm reading through notifications in chronological order, so I hadn't noticed.)

componentDidUpdate(prevProps,prevState) {
// Callbacks (side effects) are only safe after commit.
if (this.state.someStatefulValue!==prevState.someStatefulValue) {
this.state.onChange(this.state.someStatefulValue);

Choose a reason for hiding this comment

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

this.props.onChange

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

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

Whoops. Thanks.

@trueadm
Copy link
Member

trueadm commentedDec 9, 2017
edited
Loading

Note: this isn't really that relevant to this PR proposal.

@markerikson In my opinion, something in state doesn't have to mean it anything to do with rendering. I think we have an opportunity to change that in the future and simplify things too. I attempted to tackle these problems with Inferno and they were very received – so I feel we can do better here.

markerikson reacted with thumbs up emoji

@markerikson
Copy link

@trueadm : I think the reason for that rule of thumb comes from this:

  • You shouldn't mutatethis.state
  • CallingsetState() queues a state update, but also a re-render
  • Updating a jQuery plugin or some other non-HTML-ish API isn't something that goes inrender, so you need to apply the updates to that in another lifecycle method
  • Actual re-rendering is almost definitely wasted CPU cycles for this scenario
  • Thus, you don't want to callsetState() at all, so that you don't trigger pointless re-renders

If you've got some ideas that work well as an alternative to that mindset, I'm certainly curious to see what you have in mind.

vcarl and guillaume86 reacted with thumbs up emoji

@OliverJAsh
Copy link

WillgetDerivedStateFromNextProps be called for the initial render? The RFC only mentions it as a replacement forcWRP.

@BTMPL
Copy link

It will not be called in the current implementation but it is under consideration.

@bvaughn
Copy link
CollaboratorAuthor

bvaughn commentedJan 17, 2018
edited
Loading

The new lifecycle allows to use with functional components, as its static and js allows it. It must be documented what React will do in this case.

Lifecycle hooks apply to class components only. I thinkthe existing documentation already makes this pretty clear. Once we add docs for the new (static) lifecycle methods we'll hopefully make that clear as well.

The docs are a community effort too, so if you think it's not clear enough- PRs are welcome.

Edit: I've added a TODO to my PR to detect and warn about this case in DEV mode.

@bvaughn
Copy link
CollaboratorAuthor

WillgetDerivedStateFromNextProps be called for the initial render? The RFC only mentions it as a replacement forcWRP.

The RFC isslightly out of date with respect to a couple of things. (I'll probably update it once I've posted an implementation PR.) This is one of them.

Our current thinking is thatgetDerivedStateFromPropswill be called after construction for the initial state.

@myitcv
Copy link

Really glad to see this being discussed because it's a point we've debated/worked on a great deal 👍

This is a fairly lengthy thread so apologies if my comments end up repeating any of the points I've missed here or in the various code reviews.

PureComponent's

The one point I haven't seen discussed here is that ofPureComponent's. We usePureComponent's across the board (because we use ImmutableJS exclusively) and one of the issues we've found with the current API is the sequence of events when it comes to updating props. The sequence of events from the perspective of a child component is as follows:

componentWillReceivePropsshouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate

As things currently stand in React, even if props donot change (with respect to the definition ofPureComponent) then the child'scWRP still fires. We handle this by actually proxying calls (we do a load of plumbing/overriding in a common base class) and only forwarding thecWRP call to our components if we determine the props have changed (calculated by the same shallow comparison asPureComponent'sshouldComponentUpdate, because only in this situation would the subsequentshouldComponentUpdate return true). Whilst this does introduce some inefficiency, it does mean thatcWRP has much clearer semantics in our components.

Common props handling

As things currently stand, we almost always delegate from a component'scWM andcWRP to acommonProps() method which has access to two helper properties:props andprevProps.props gives us the new/next props,prevProps (as its name suggests) gives us the previous props.prevProps can only be called from within (transitively)cWM orcWRP (although I think this restriction is superfluous).

(We implemented these as properties given the pre-existingprops property incidentally.)

commonProps() is the point where we derive state from props (per this proposal), hence having access to the previous props is critical at this point (even though we know that the propshave changed, because of the use ofPureComponent).

MakingsetState sync

We have also madesetState() a synchronous operation. I asked a question about why it was decided it should be async inthe React Discussion Forums; there is also anopen issue discussing this point.

We found thatsetState()not being synchronous leads to either mistakes or rather convoluted chained code where a value fornextState is passed from method to method, with one of those methods finally callingsetState().

Memoized values

I've seen the discussion about putting memoizing logic intorender(). This is fine except in those situations where you need the memoized values in places other thanrender() (i.e. callbacks etc). In which case these memoized values have to go into state. But this is fine: because your trigger for recalculating the memoized values is either a change in props (cWM/cWRP) or a change in state. In either case the component is going to re-render (by definition), so updating state further is effectively at no extra cost. Again this is where havingsetState() be synchronous helps to keep code simpler in our experience.

How this maps to the current proposal

All of that said, I have the following questions with respect to this proposal/thread:

  • getDerivedStateFromNextProps sounds very similar to the approach we follow withcommonProps. Just to confirm, this method will only fire when the props have actually changed (see point aboutPureComponent's)?
  • I understand the point aboutcomponentDidMount being a better place to setup event listeners. As mentioned above,cWM is one of the entry points tocommonProps (our method for deriving state from props). So are you proposing that the first timegetDerivedStateFromNextProps would be called is after the initialrender?
  • Any thoughts on the question aboutsetState? Or indeed our approach of having made it synchronous? We can continue that discussion in the linked issue if you feel it's a better place for it, but as you can see we found it very much linked to the lifecycle points in this thread

Thanks in advance.

@bvaughn
Copy link
CollaboratorAuthor

getDerivedStateFromNextProps sounds very similar to the approach we follow withcommonProps. Just to confirm, this method will only fire when the props have actually changed (see point aboutPureComponent's)?

This method will be called at the same time and in the same cases ascomponentWillReceiveProps is currently. This RFC does not propose any changes to the method with regard to shallow props comparison.

I understand the point aboutcomponentDidMount being a better place to setup event listeners. As mentioned above,cWM is one of the entry points tocommonProps (our method for deriving state from props). So are you proposing that the first timegetDerivedStateFromNextProps would be called is after the initial render?

As mentioned inthe comment above yours, our current thinking is thatgetDerivedStateFromProps will be called after construction (for the initial state) as well.

Any thoughts on the question aboutsetState? Or indeed our approach of having made it synchronous? We can continue that discussion in the linked issue if you feel it's a better place for it, but as you can see we found it very much linked to the lifecycle points in this thread

I understand why you mention it, but I don't think this (already pretty noisy) discussion is the best place to have this chat. 😄

@myitcv
Copy link

Thanks for the quick response@bvaughn

This method [getDerivedStateFromProps] will be called at the same time and in the same cases as componentWillReceiveProps is currently. This RFC does not propose any changes to the method with regard to shallow props comparison.

Is there scope for extending this proposal to include consideration ofPureComponent? Because otherwise logic withingetDerivedStateFromProps will be called needlessly, negating the point/benefit ofPureComponent.We can of course continue to proxy as I described, but I wonder whether we kill two birds with one stone and fixPureComponent as part of this proposal.

As mentioned in the comment above yours, our current thinking is thatgetDerivedStateFromProps will be called after construction (for the initial state) as well.

Sorry, I've just now seen that response. Makes total sense. Sounds likegetDerivedStateFromProps will be a slot in replacement for ourcommonProps, notwithstanding the point aboutPureComponent above.

I understand why you mention it, but I don't think this (already pretty noisy) discussion is the best place to have this chat.

Noted 👍 - perhaps I can encourage you /@gaearon / others to chime in on either the linked issue or the React Discuss thread?

@bvaughn
Copy link
CollaboratorAuthor

Is there scope for extending this proposal to include consideration ofPureComponent? Because otherwise logic withingetDerivedStateFromProps will be called needlessly, negating the point/benefit ofPureComponent.

It seems like you're concerned about the price of computing updatedstate fromprops ifprops haven't changed? A common pattern for dealing with this is to only update values instate if the correspondingprops values have changed. (Assuming your component has more than one prop, you'd likely need to do this checkanyway to ensure that theprops yourstate care about have changed.)

I don't think this negates the value ofPureComponent at all. Typically, the most expensive part of a component updating is therender (and all of the subsequent children updates).

As for the other discussion thread, I'm sure one of us will try to comment when we can. There's a lot of discussion threads and not many of us though. 😅 Thanks for bring it to attention again. Sorry!

if (this.state.externalData===null) {
// Prime an external cache as early as possible.
// (Async request would not complete before render anyway.)
asyncLoadData(this.props.someId);
Copy link

@namuolnamuolJan 17, 2018
edited
Loading

Choose a reason for hiding this comment

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

I realize why this is being done, but should side effects inrender really be encouraged for such a common pattern?

streamich and Jessidhia reacted with thumbs up emoji
Copy link
Member

Choose a reason for hiding this comment

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

Hopefully it will be pretty rare, andcomponentDidMount will work for most use cases.

But I wonder, why don't you begin loading async data in the constructor? Would that be encouraged?

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

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

I was hesitant to show this pattern on the RFC, because it's only intended for rare, advanced use cases (eg a library like Relay). I'm a little bummed that it's drawn a bit of negative attention, but I understand why. I should have probably labeled it much more clearly with a disclaimer or something.

That being said, youcould trigger the load in the constructor. Side effects in the constructor feel worse to me than side effects inrender, but neither is great. Timing wise though, it would be about the same. (render will be called synchronously, shortly after the constructor.)

@Ephem
Copy link

I really approve of this proposal!

When it comes tocomponentWillMount and SSR specifically I have personally never seen a valid use-case for cWM in a SSR-app, but I have seen tons of invalid uses of it due to misconceptions. Marking it unsafe would definitely help clear this up.

bvaughn reacted with thumbs up emoji

@myitcv
Copy link

@bvaughn

It seems like you're concerned about the price of computing updated state from props if props haven't changed? A common pattern for dealing with this is to only update values in state if the corresponding props values have changed. (Assuming your component has more than one prop, you'd likely need to do this check anyway to ensure that the props your state care about have changed.)

I don't think this negates the value of PureComponent at all. Typically, the most expensive part of a component updating is the render (and all of the subsequent children updates).

You make a very fair point; we do indeed end up comparing fields betweenprops andprevProps to conditionally recalculate derived state.

So I think my "unease" simplifies then to the incongruence ofgetDerivedStateFromProps being called when, as aPureComponent, youknow the props haven't changed (becauseshallowEqual(nextProps, prevProps) === true). Otherwise don't we end up having to document that it's called regardless?

But then perhaps I'm only pushing this because I was surprised when I first stumbled across this in the current implementation!

As for the other discussion thread, I'm sure one of us will try to comment when we can. There's a lot of discussion threads and not many of us though. 😅 Thanks for bring it to attention again. Sorry!

No apology needed :)

@bvaughn
Copy link
CollaboratorAuthor

Otherwise don't we end up having to document that it's called regardless?

But then perhaps I'm only pushing this because I was surprised when I first stumbled across this in the current implementation!

For what it's worth,the docs do point this out 😄

componentWillReceiveProps()

Note that React may call this method even if the props have not changed, so make sure to compare the current and next values if you only want to handle changes. This may occur when the parent component causes your component to re-render.

@myitcv
Copy link

For what it's worth, the docs do point this out 😄

🤦‍♂️ - I think I'll go and take a break :)

Thanks, I must have missed this before.

bvaughn and RoyalIcing reacted with thumbs up emoji

@bvaughn
Copy link
CollaboratorAuthor

No worries. It happens~

@bvaughn
Copy link
CollaboratorAuthor

This RFC has been implemented viafacebook/react/pull/12028 which was merged this morning.

@acdliteacdlite merged commitf01ab98 intoreactjs:masterJan 19, 2018
@bvaughnbvaughn deleted the static-lifecycle-methods branchJanuary 19, 2018 21:15
@TrySound
Copy link
Contributor

When we can expect 16.3 release?

@bvaughn
Copy link
CollaboratorAuthor

To be determined.

This method is also invoked before a mounted component receives newprops. Return an object to update state in response to prop changes. Return nulltoindicate no change to state.

React does notcall this methodbeforetheintial render/mount and so itisnot called during server rendering.
Note that React maycall this methodeven iftheprops have not changed. If calculating derived dataisexpensive, compare next and previous props to conditionally handle changes.

Choose a reason for hiding this comment

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

@bvaughn ThePR for React included
a note thatgetDerivedStateFromProps doesn't receive previous props. Should this sentence be updated?

Copy link
CollaboratorAuthor

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 shownin this example

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 viaprevState, right? Alright then, I thought this was left over since I think theprevProps parameter got removed in a later iteration.

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 ourprevious 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
CollaboratorAuthor

Choose a reason for hiding this comment

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

Not allprops, only ones that are directly used to derivestate, and only if it's an expensive computation.

Copy link
Member

@gaearongaearonJan 26, 2018
edited
Loading

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 theprevious version. Which is a special case of an accumulation (that happens to completely discard the current value). Accumulation is exactly the purpose ofgetDerivedStateFromProps 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 inrender". Personally I find this a bigger / less intuitive exception to a rule thanprevProps's nullability (the reason for which is pretty obvious -- noprevProps 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! 😊

bvaughn reacted with thumbs up emoji
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. Seefacebook/react#10580.

bvaughn reacted with thumbs up emoji

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 ofstatic 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, henceprevProps is not nullable and an explicitboolean 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
CollaboratorAuthor

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!

componentDidMount() {
// Wait for earlier pre-fetch to complete and update state.
// (This assumes some kind of cache to avoid duplicate requests.)
asyncLoadData(props.someId).then(externalData=> {

Choose a reason for hiding this comment

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

this.props ?

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

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

This PR was merged 9 days ago 😄 but that typo has been fixed in master.

@reactjsreactjs locked asresolvedand limited conversation to collaboratorsJan 29, 2018
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.

Reviewers

@sebmarkbagesebmarkbagesebmarkbage left review comments

@gaearongaearongaearon left review comments

@trueadmtrueadmtrueadm left review comments

+19 more reviewers

@jlongsterjlongsterjlongster left review comments

@aickinaickinaickin left review comments

@TrejGunTrejGunTrejGun left review comments

@koba04koba04koba04 left review comments

@namuolnamuolnamuol left review comments

@sompylasarsompylasarsompylasar left review comments

@greggbgreggbgreggb left review comments

@markeriksonmarkeriksonmarkerikson left review comments

@billyjanitschbillyjanitschbillyjanitsch left review comments

@ericvicentiericvicentiericvicenti left review comments

@jamespleasejamespleasejamesplease left review comments

@RoyalIcingRoyalIcingRoyalIcing requested changes

@taiontaiontaion left review comments

@myitcvmyitcvmyitcv left review comments

@clemmyclemmyclemmy left review comments

@amannnamannnamannn left review comments

@TrySoundTrySoundTrySound left review comments

@philrajphilrajphilraj left review comments

@j-f1j-f1j-f1 left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Labels

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

33 participants

@bvaughn@abritinthebay@trueadm@sebmarkbage@markerikson@aickin@quantizor@gaearon@BTMPL@philraj@kof@TrySound@djfarly@istarkov@OliverJAsh@myitcv@Ephem@jlongster@TrejGun@koba04@namuol@sompylasar@greggb@billyjanitsch@ericvicenti@jamesplease@RoyalIcing@taion@clemmy@amannn@j-f1@acdlite@facebook-github-bot

[8]ページ先頭

©2009-2025 Movatter.jp