React: Documentation for componentWillReceiveProps() is confusing

Created on 7 Apr 2015  Â·  9Comments  Â·  Source: facebook/react

If you read this doc: https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops - it says: "Invoked when a component is receiving new props."

However it's not clear on why componentWillReceiveProps() is still being invoked when a component does not explicitly receive any props. For example: http://jsbin.com/nawucuseqo/1/edit?js,console

Looks like by default componentWillReceiveProps() is called every time during updating phase, and you, as a developer, have no control over that.

Most helpful comment

When you call setState on a component, its componentWillReceiveProps is not called (e.g., Application in your example does not have componentWillReceiveProps re-called). React doesn't make an attempt to diff props for user-defined components so it doesn't know whether you've changed them. In your case where the props object is empty it's pretty clear but oftentimes a prop is a complex object or function that's hard or impossible to diff, so we call it always (and rerender always) when a parent component rerenders.

Note also that the behavior here may change slightly after #3226 – if you have a JSX object that doesn't depend on the parent's props at all, it'll be hoisted outside the React.createClass definition by a smart compiler. If you were to write React.createElement(Button, {test: this.props.test}) then it would always rerender though, even if this.props.test is always the same (because it's hard to determine that statically).

All 9 comments

Receiving a call to componentWillReceiveProps every time render is called is perfectly within the guarantees of the API. There is no promise that the props will be different from the previous time the function was called (nor is it guaranteed that there will be any props); the only guarantee is that the function will be called if the props change.

There shouldn't be a need for you to "control" when componentWillReceiveProps is called. If your component doesn't have any work to do for the new props, it can just return.

the only guarantee is that the function will be called if the props change.

Can you share an example where componentWillReceiveProps() is _not_ being called?

When you call setState on a component, its componentWillReceiveProps is not called (e.g., Application in your example does not have componentWillReceiveProps re-called). React doesn't make an attempt to diff props for user-defined components so it doesn't know whether you've changed them. In your case where the props object is empty it's pretty clear but oftentimes a prop is a complex object or function that's hard or impossible to diff, so we call it always (and rerender always) when a parent component rerenders.

Note also that the behavior here may change slightly after #3226 – if you have a JSX object that doesn't depend on the parent's props at all, it'll be hoisted outside the React.createClass definition by a smart compiler. If you were to write React.createElement(Button, {test: this.props.test}) then it would always rerender though, even if this.props.test is always the same (because it's hard to determine that statically).

so we call it always (and rerender always) when a parent component rerenders.

I think this would be a very useful addition to the docs. I appreciate the explanation, @spicyj.

I agree that the docs are confusing.

We don't use componentWillReceiveProps for props, but to setState with data from the stores. @spicyj, your explanation makes me wonder if this pattern will break in the future?

@cody Please do not rely on componentWillReceiveProps() always being called. The API does not promise to always call the function, though in practice it is currently getting called. We may optimize the calls to this function and call it more intelligently (less often) without notice. The only guarantee is that it will be called if props change.

@jsfb What I like about React is its predictability, which is archived through complete rerenders.

Let's image a componentWillReceiveProps() that is guaranteed to be called for each render cycle. When the stores have changed, then a forceUpdate() could be called on the top component. Everything would get rerendered and the controller-views could use componentWillReceiveProps() for getting the global state from the stores. That would be easy and predictable.

But there is no lifecycle method that can do this, so I have to find another way. The solutions that I have found so far work as follow: The controller-views use one lifecycle method to initialize from the stores, another one for registering with individual stores and yet another one for unregistering. Every store that changes calls the callback in each registered controller-views. Those callbacks execute setState()which rerenders its subtree. Just the subtree, so the idea of rerender everything is lost. The docs say about setState()that "calls may be batched". They may or they may not, which means predictability is lost too.

All lifecycles methods are so much specialized, that we have to use complicated solutions even for simple things.

I am probably just missing something, but I don't know what.

@cody I think the weird thing is that you are using componentWillReceiveProps to copy the data from your stores into your component local state. If you want something that will be called on each render cycle, that thing is called "render". Why wouldn't the component's render function just get the data from the store as-needed to render?

IMPO, the vast majority of components shouldn't need a componentWillReceiveProps(). That function is more of an escape hatch for exceptional situations (in particular, detecting and responding to a props change). For instance, suppose you were implementing a video player component, that took a status prop with value "playing" or "stopped". When you transition from playing to stopped, or vice versa, you need to do some setup/cleanup or call an imperative API. This use case aligns perfectly with the documented API (if props change, function will be called, else, no guarantees).

@jsfb

If you want something that will be called on each render cycle, that thing is called "render". Why wouldn't the component's render function just get the data from the store as-needed to render?

I did not like that, because the data from the global state is needed when shouldComponentUpdate gets called. But now I think this might be the best solution. In the top component's render function all data from all stores gets put into props and then the controller-views can pick what they need. Normally this would be crazy, but by using immutable.js that could work. I will try.

Thanks for the discussion.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tleunen picture tleunen  Â·  3Comments

UnbearableBear picture UnbearableBear  Â·  3Comments

jvorcak picture jvorcak  Â·  3Comments

zpao picture zpao  Â·  3Comments

varghesep picture varghesep  Â·  3Comments