Closed sebmarkbage closed 6 years ago
I mostly use cWM to perform setState based on initial props, since cWRP doesn't fire initially. I could do it in constructor, but I try and avoid extending that method when possible because I have an allergy to super calls :/
@yaycmyk If you could use field initializers would you use that instead?
class Foo {
state = { data: this.props.initialData };
...
}
or for something more complicated:
class Foo {
state = this.computeInitialState();
computeInitialState() {
var state = { data: null};
if (this.props.something) {
state.data = this.props.somethingElse;
}
return state;
}
...
}
for createClass cWM is the only place to do constructor stuff, since you can't actually define a constructor. I need to do a grep through some of our code bases I think there are a couple other use cases/optimizations that use it I'm not remembering
@sebmarkbage I do use property intializers, though they become clumsy if you need a non-trivial amount of Boolean logic. To be fair, they do cover 95% of all my use cases. cWM is a rare sighting in our products
For server rendering, componentWillMount fires but not componentDidMount.
What would replace the browser-only nature of componentDidMount? Adding if (typeof window === 'undefined')
expressions to detect render mode in componentDidMount seems dirty compared to the current lifecycle function approach.
@mars componentDidMount
would still only fire on the client. Do you have a use case in mind for componentWillMount
on the server?
I only use it to set up data structures on this
for things like memoization, but with ES6 class constructors there's no reason for it anymore.
After looking back at my last app that used server-rendering, the only componentWillMount use case is an initial setState
. So, following @sebmarkbage's suggestion to conditionally setState
in componentDidMount would be a fine way to allow deprecation of componentWillMount.
This issue is probably because of some of my recent hackery. Today, given that when componentWillMount
is called you can actually trust that it will be mounted, you can do stuff like register descendants with an ancestor and use that information in render in another ancestor.
http://codepen.io/anon/pen/gwbEJy?editors=0010
As a sort of DOM parallel, when you have a form that has two inputs where one of them is a button, then "keyDown" of a text input causes the form to submit. In other words, the presence or non-presence of another ancestor changes the behavior of another ancestor element. Also, radio groups.
Not saying "keep componentWillMount" but it's a great way to create relationships between ancestors of a context. It would be nice to either 1) have a componentMountCancelled (sounds unlikely, or 2) call cDM on the server (???) because that's the only reason I do this in cWM or 3) have some first class API for building this kind of relationship between ancestors in the same context.
I'd like (3), but have no idea what that looks like.
For the use-case here, it's in the React Router v4 API:
<div>
<Match pattern="/users" component={Users}/>
<Match pattern="/about" component={About}/>
<Miss component={NoMatch}/>
</div>
If no <Match/>
matches the location, then the descendant <Miss/>
components in context will be rendered (and it's beautiful, isn't it?!)
Only reason this matters is server rendering. Thanks to some talks with @sebmarkbage we've got a plan B, but I am a little 😢 about this. (I think we might get a flicker when we move to cDM ... which is not exciting.)
I'm pretty sure @kenwheeler's new <Song>
employs this technique also, but could probably move to cDM
.
@ryanflorence Why not just extend constructor in that case though? cDM is really a last resort since it's way less efficient from needing a second render pass.
Would there ever be a case where we'd need to compute some data or perform a certain routine before the component mounts, that we otherwise wouldn't be able to do in the component constructor?
What if we needed or wanted to fire off a Redux action before the component mounts for the first time? Or what about wanting to attach an event listener to the component before it mounts?
I'm just worried that there might be some niche use cases for componentWillMount
that we aren't thinking of here.
@ajwhite I'm curious what you think about this
What if we needed or wanted to fire off a Redux action before the component mounts for the first time?
@nickzuber I was just coming to mention this. I work on a rails app that uses react-rails for server rendering a collection of smaller react/redux client apps. A pattern that is working well for us is to dispatch actions that populate our reducers with initial data in componentWillMount
. That way you can easily pass data into your app from the rails view layer using react_component, and populate the state before rendering.
@ryanflorence This is more in response to Fiber and implementing componentWillUnmount
.
For your use case, you don't really need componentWillUnmount
since the use case is on the server anyway. You also don't need setState
. So, technically you can still do the same thing in the constructor even though it is frowned upon. :) However, I think that for your use case a server-side-only componentDidMount
might make the most sense (componentDidServerRender
?). It would fire after rendering so it still requires two-passes but preserves the other capabilities.
@aflanagan Can you use the field initializer pattern (or constructor) that I used above to initialize the state from the props?
@sebmarkbage ah yes, I think we could do that. We adopted the cWM
strategy before we moved to es6 classes, and I hadn't considered that we could start doing that in the constructor.
To help tackle the server use case, what about a new method like componentDidRender? For the browser lifecycle, it'd come before cDM. The idea being the JSX has been rendered/"compiled" into vdom but not yet mounted somewhere.
There is one use case, that is also the same as the only use case for componentWillUpdate
that I know about. Basically, you read some global mutable state so that you can reset it later in componentDidUpdate
, after the children has already rendered.
For example, if the children rendering causes the scroll position of a page to change.
class ScrollRestore {
componentWillMount() {
this._scroll = Global.scrollTop;
}
componentDidMount() {
Global.scrollTop = this._scroll;
}
componentWillUpdate() {
this._scroll = Global.scrollTop;
}
componentDidUpdate() {
Global.scrollTop = this._scroll;
}
...
}
Technically, you can do the same thing in the constructor but it is kind of iffy to have impure constructors.
Just wanted to chime in that componentWillMount
causes a lot of confusion in the server-rendering and side-loading use cases where it seems like a good option but doesn't really work the way you expected since it's synchronous. I think the only use case that would have to be reconsidered with this deprecation is when you're still using React.createClass
and don't have a convenient way to add to the constructor.
Airbnb's primary use cases for componentWillMount
are to seed our global translation singleton with phrases, which come from props, are needed in the render path, and are not necessarily available at construction time; and to bootstrap Alt stores with props.
All of our translations will break in the former case, if this lifecycle method is deprecated. Since we primarily handle phrases at the top level of a tree, ordering nondeterminism is not a concern for us.
In addition, constructors should never have side effects - componentWillMount
provides us with an appropriate place to put side effects, that should basically only happen once per environment, that executes both on the client and server (componentDidMount
, of course, would be for client-only).
Thought this GitHub search might be useful to see some use cases, sorted by recently indexed so it represents how people are actively using componentWillMount in the wild today.
Note: You must be logged in to search for code across all public repositories
@ljharb So, it's basically lazy initialization of global state. We do a lot of lazy initialization in render
methods, but since it is generally unobserved I don't consider that bad practice. The difference here is that it is not just lazy but dependent on props.
I guess you assume that props will never change and that you won't ever have two different subtrees with different phrases. Otherwise, people normally use context
for this use case.
Given that there are already a lot of assumptions involved in this particular use case, maybe it's not that bad to just do it in the constructor as a hack?
How do you do this server-side? Do you assume that the context is only used for a single request at a time? No async rendering?
I'd very strongly prefer something besides the constructor. Hmm, I thought context
was deprecated already :-p is it not?
Yes, that's right - whether it was async or not, it would be re-evaluated and reconstructed in a new vm
for every request.
If componentWillUpdate
, or componentWillReceiveProps
, or something similar, could be sure to run upon every new set of props, including the first/initial one, prior to the render - that would suffice for our use cases, I think, and would probably solve them more robustly than componentWillMount
. Is adding a new lifecycle method out of the question?
Adding a consistent lifecycle method to handle props would be super useful in many cases. The most common being "fetch the x for this.props.xId". The benefit of running before render is that you can show a loading indicator without requiring a second render. The problem is that for some use cases you'd want it to run on the server, like @ljharb's, but for the fetching data you'd want that on only client in most cases.
Edit: maybe this should branch out to another issue.
Is there ever a time (in the browser context) where componentWillMount is not called immediately after the constructor?
As far as I can remember, componentWillMount is effectively a constructor function. It served that purpose before ES6 classes were adopted that came with their own constructors. In fact, in much earlier versions of React, componentWillMount was always called on construction IIRC which made server rendering painful. So we changed componentWillMount to be called only in the browser render context, and not in the server render context. (Then getInitialState was the effective constructor)
All the uses I've seen for componentWillMount are just constructors which you expect to only run in the browser. If componentDidMount isn't the right place to move that code (due to double render) then moving it into the class constructor with a typeof window
if check might be adequate.
I don't see the reason to be concerned about doing side effects in a React component constructor if you're not concerned about doing the same within componentWillMount which is part of component construction. Since user code never instantiates components directly (library code does), the typical reasons to avoid side effect constructors doesn't apply for React components.
Sebastian, maybe another option is to slightly change the behavior of componentWillMount rather than remove it by making it more explicitly part of construction just to avoid user code writing that browser check and preserve the majority of existing code? Would that be possible within Fiber?
I don't think I ever used componentWillMount
for anything else than performing a setState(valueDerivedFromProps)
, which can be performed in constructor
now.
The only blocker here would be that constructor
isn't a React.createClass
config field. For that matter, as there are differences between the React.createClass
& the ES6 class-based APIs, I see three possibilities:
React.createClass
API completely (I don't know if it's planned for a foreseeable future)React.createClass
that is consistent with the ES6 class constructor
behaviourcomponentWillMount
name only for React.createClass
but change its behaviour to match constructor
@ryanflorence For the example in your codepen, you can just pass the extra context
argument to the super
function which will inject it in this.context
(see in ReactComponent.js ) so that you can have access to this.context
in the constructor
:
class Widget extends React.Component {
constructor(props, context) {
super(props, context)
this.context.register()
}
componentWillUnmount() {
this.context.unregister()
}
render() {
return React.Children.only(this.props.children)
}
}
If there will be alternative do cWM to use in server rendering, then cWM deprecation seems fine. Something like componentWillServerRender, as said before, would be great.
@leeb
All the uses I've seen for componentWillMount are just constructors which you expect to only run in the browser.
This doesn't sound right to me. componentWillMount, unlike componentDidMount, does run on the server.
@sebmarkbage @bloodyowl if I do work in the constructor, and the component never actually mounts, I still have the same problem as cWM, don't I? I have no chance to unregister. The problem for me is that I don't know before render if the thing will actually be rendered. Or, if it doesn't get rendered, I have no way of knowing it got aborted.
If there are multiple inheritance levels, componentWillMount will execute after the object is fully constructed.
We use componentWillMount
in 50+ components on mobile.twitter.com to:
Most if not all of this looks like it could be done in the constructor
or componentDidMount
if necessary.
Some clarification: getInitialState
(for createClass), constructor
and componentWillMount
always happen at the same time and have the same capabilities. The only difference is that componentWillMount
can use this.setState
where as the others have to return or initialize the state
field.
Meaning, that technically you can do everything you can with getInitialState
/constructor
still. However, there is already a good understanding and practice that you don't do side-effects in those.
The reason to deprecate componentWillMount
is because there is a lot of confusion about how it works, and what you can safely do in it, and usually the answer is nothing. It is a life-cycle with void return so the only thing you can do is side-effects, and it is very common to use them for things like subscriptions and data fetching without realizing the edge cases where it won't work out well.
The purpose would be to highly discourage side-effects, because they have unforeseen consequences, even though you technically could do them in the constructor.
That in turn, would help upgrading to something like Fiber without expecting a lot of race conditions to break your app in subtle way.
@ryanflorence You would feature test and only do that work in the constructor
if you're on the server, and on the client you'd use componentDidMount
. However, an explicit server-only componentDidServerRender
would be better.
@mstijak We generally discourage deep inheritance. That's a design decision we've made due to complications like this one. Even if you do use it, it is common practice to not rely on super-classes calling back up to sub-classes for initialization purposes.
@michalwerner Would it be ok for the server-side only alternative to only fire after a component tree has rendered? I.e. semantically how componentDidMount
works. Or do you rely on it being invoked early?
@sebmarkbage On the server, the sole purpose is to produce HTML, so doing things after rendering seems pretty useless overall :-)
@ljharb The use case for @ryanflorence is to throw away the buffered HTML and do a redirect or render a different response based on what was rendered. This doesn't work with streaming necessarily but works well for async or parallelized/distributed rendering.
Another use case is logging components that were rendered but that doesn't have to be done before the HTML is produced.
Only using it so I can avoid annoying constructors (it's more verbose, have to call super, etc) just to set the initial state
@sebmarkbage we've got two use-cases 1) know there was a miss, but keep the HTML and use a 404 status, 2) throw away the HTML and redirect instead. In either case, as we discussed privately, we can do a two pass render for (1)
The only use case for me for using componentWillMount
is to avoid this error
Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component
As in constructor state which depends on props should be defined without calling setState
, but that state change code could be reused in other parts.
BTW I like the idea to remove componentWillMount
as it has no advantages over the constructor
but provide a lot of questions about how it differs.
@sebmarkbage I think most of the cases can be solved by simply using the context to store whether or not the component will mount on the server. context.mountEnv
. It would make it extremely simple. For airbnb's use case creating a HOC that defines childContextTypes = {mountEnv<string>}
and does the init of translations and whatnot on the constructor would be enough. The Wrapped component can always be used to store statics if props alone is not enough to do this bootstrapping.
I can't think right now of something that cannot be done with constructor alone. I would probably start throwing a warning on the next version and deprecate it a bit later.
I have been using componentWillMount
for a dependency library which wraps components in a HOC to load data dependencies both on client and server. It works by keeping track of all the loaded dependencies using promises and calling renderToString
multiple times until there are no longer new dependencies to load.
But thinking on it now, componentWillMount
should not be necessary for this. you could simply use constructor/getInitialState
for calling the promises and then set state synchronously on next renderToString
call once the dependencies have been loaded. Client can still use componentDidMount
for setting the state.
I think it makes sense to deprecate componentWillMount
, even from the point of isomorphic applications. I also don't think that it's necessary to add a componentDidServerRender
method as you can easily code that logic yourself in the constructor.
@AlexGalays wrote: Only using it so I can avoid annoying constructors (it's more verbose, have to call super, etc) just to set the initial state While this may not be the most important argument for keeping the componentWillMount
, it is an important one.
Can't you not call this.setState() in component will mount? Component will mount is only supposed to be for setting up listeners. Not to say, you can't do the same thing in ComponentDidMount but the main difference in my opinion is that setState() always works in componentDidMount() while it will not work in componentWillMount(). ComponentWillMount() and componentWillUnmount() have been perfect and explicit places to setup and tear down listeners without cluttering up componentDidMount() which often has other logic as well. Just my 2 cents.
@catmando @AlexGalays I think it would be more like a discouraging use, then a soft deprecation. A hard deprecation would take a long time and I'd expect that people would be using field initializers prevalently by then. They also let you avoid creating the super
bloat of constructors.
@jkol36 That's not correct. setState()
works in componentWillMount
too which avoids a second pass to render.
@sebmarkbage if you are right about that than I stand corrected. I do remember instances though where I would get an error saying something like "can't call setstate on mounting component" when trying to call setState() in componentWillMount()
Is there anyway to know that a component rendered on the server w/ fibers? There is no cDM there, all we have is the constructor (or cWM) and we can't trust those.
My current approach is to send an object in and mutate it, check the object to see if we need to do a second pass render, and then use that object for the second pass to know what to render differently this time.
But as far as I can tell, there is no way to know something did or didn't render on the server.
There's not a single componentWillMount
in any of my projects, never understood why it was needed.
I just define static methods for fetching data on the server side over which I have full control and know exactly how and why they are called.
generally I do not agree with componentDidMount. React lifecycle is like getDefaultProps, getIniitialState, componentWillMount, render, componentDidMount. And If you set state in componentDidMount you will rerender component, so render is called twice
@sebmarkbage So for example if I use React.createClass and use componentWillMount to fetch same data and call setState() inside then better will be use getInitialState and do same inside getInitialState ?
@w-site see componentDidMount docs
then better will be use getInitialState and do same inside getInitialState ?
You can't use getInitialState
for fetching data because it is synchronous.
Let's use this thread to discuss use cases for componentWillMount and alternative solutions to those problems. Generally the solution is simply to use componentDidMount and two pass rendering if necessary.
There are several problems with doing global side-effects in the "componentWill" phase. That includes starting network requests or subscribing to Flux stores etc.
1) It is confusing when used with error boundaries because currently
componentWillUnmount
can be called withoutcomponentDidMount
ever being called.componentWill*
is a false promise until all the children have successfully completed. Currently, this only applies when error boundaries are used but we'll probably want to revert this decision and simply not callcomponentWillUnmount
here.2) The Fiber experiment doesn't really have a good way to call
componentWillUnmount
when a new render gets aborted because a higher priority update interrupted it. Similarly, our sister project ComponentKit does reconciliation in threads where it is not safe to perform side-effects yet.3) Callbacks from
componentWillMount
that update parent components with asetState
is completely unsupported and lead to strange and order dependent race conditions. We already know that we want to deprecate that pattern.4) The reconciliation order of children can easily be dependent upon if you perform global side-effects in
componentWillMount
. They're already not fully guaranteed because updates can cause unexpected reconciliation orders. Relying on order also limits future use cases such as async or streaming rendering and parallelized rendering.The only legit use case for
componentWillMount
is to callthis.setState
on yourself. Even then you never really need it since you can just initialize your initial state to whatever you had. We only really kept it around for a very specific use case:When the same callback can be used both synchronously and asynchronously it is convenient to avoid an extra rerender if data is already available.
The solution is to split this API out into a synchronous version and an asynchronous version.
This guarantees that the side-effect only happens if the component successfully mounts. If the async side-effect is needed, then a two-pass rendering is needed regardless.
I'd argue that it is not too much boilerplate since you need a
componentWillUnmount
anyway. This can all be hidden inside a Higher-Order Component.Global side-effects in
componentWillReceiveProps
andcomponentWillUpdate
are also bad since they're not guaranteed to complete. Due to aborts or errors. You should prefercomponentDidUpdate
when possible. However, they will likely remain in some form even if their use case is constrained. They're also not nearly as bad since they will still get theircomponentWillUnmount
invoked for cleanup.