-
Notifications
You must be signed in to change notification settings - Fork 7.6k
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
Discussion: componentWillReceiveProps vs getDerivedStateFromProps #721
Comments
|
Came to report the same thing. In our case, we use https://github.com/final-form/react-final-form, and wanted to clear the value of one field when another field changed (think 2 dropdowns, one with car manufacturer and one with model - when manufacturer changes, clear model). Then we had to compare old props with new props without putting it in We've since migrated to use https://github.com/final-form/final-form-calculate which allows us to do the same thing declaratively, but I still think it's a valid use case. (I guess the answer, if we had not moved away from it, would have been "lift state up". Which is fair enough 🙂)
The blog post encouraged opening issues here: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#other-scenarios |
Alright, let's leave it open then |
I added another use-case into (the duplicate) issue #729 |
Can you explain why Before: componentWillReceiveProps(newProps){
if (this.props.visible === true && newProps.visible === false) {
registerLog('dialog is hidden');
}
} After: componentDidUpdate(prevProps){
if (prevProps.visible === true && this.props.visible === false) {
registerLog('dialog is hidden');
}
} That’s the exact use case for |
I think in some cases moving a prop to Assume you need to perform an impossibly expensive computation based on a prop, and you want to make sure you only perform it when the prop has actually changed. And supposedly you need the computed value for rendering. In this case, instead of doing: class Component extends React.Component {
constructor(props) {
super(props);
this.state = {
computed_prop: heavy_computation(props.value)
};
}
componentWillReceiveProps(newProps) {
if (newProps.value !== this.props.value) {
this.setState({
computed_prop: heavy_computation(new_props.value);
})
}
}
} we can instead: class Component extends React.Component {
static getDerivedStateFromProps(props, current_state) {
if (current_state.value !== props.value) {
return {
value: props.value,
computed_prop: heavy_computation(props.value)
}
}
}
} (Of course it would be nice not to store |
@gaearon - I do not see a great difference other than a mind shift I guess. I would suggest in this case to add this transition to the docs maybe. |
Since I see the title changed on this topic I'll add another use case that becomes harder (haven't interacted with react issues before so please let me know if I should open a new ticket instead :) )- We currently use |
I don’t have a great answer for you here—indeed class methods like this tend to have code that’s unsafe for async which is why we want it to be static. So yes, you’d need to pull that out if you want the code to be async-friendly. One upside of doing this is that it should be easier to test because it’s separated from the rest of the class logic. |
My primary use case is similar to @danburzo's where I need to compute state derived from a prop on initial build & whenever it changes (for example, filtering a list). The 2 solutions I arrived at are: class ListSomeThingsV1 extends Component {
static getNextState = (allTheThings) => {
return {
allTheThings,
someThings: allTheThings.filter((thing) => !thing.hide);
}
};
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.allTheThings !== prevState.allTheThings) {
return ListSomeThingsV1.getNextState(nextProps.allTheThings);
}
return null;
}
state = ListSomeThingsV1.getNextState(this.props.allTheThings);
} class ListSomeThingsV2 extends Component {
state = this.getNextState();
getNextState() {
const {allTheThings} = this.props;
return {
someThings: allTheThings.filter((thing) => !thing.hide)
}
}
componentDidUpdate(prevProps) {
if (prevProps.allTheThings !== this.props.allTheThings) {
this.setState(this.getNextState())
}
}
} Which way is preferred? Is there a cleaner alternative to both? The first isn't great because I need to keep the original object in my state just for an equality check & I need the 2nd static function to stay DRY. The 2nd just feels strange because I'm setting state in cDU so it could cause an extra render (please correct me if I'm wrong!). |
The second way is definitely worse because you're doing an extra update and because you get an intermediate render where state is inconsistent with props.
This is intentional. Forcing you to keep it in the state explicitly lets React not hold onto the previous props object in the majority of use cases in the future. So this is better from memory usage perspective. Regarding duplication, you could write it like this to avoid the duplication: class ListSomeThingsV1 extends Component {
static getDerivedStateFromProps(props, state) {
if (props.allTheThings !== state.prevAllTheThings) {
return {
prevAllTheThings: props.allTheThings,
someThings: props.allTheThings.filter((thing) => !thing.hide);
}
}
return null;
}
state = {
prevAllTheThings: [],
someThings: [],
};
} Those initial values will be overridden anyway. Don’t forget |
I think an use case missing here is the following:
|
That sounds like the main issue here. |
Sorry, I was not that clear. |
You can split your component in two. The outer one does the data fetching in |
Great to see that your approach is what I would like to have too :) Thanks Dan. |
Yeah. It’s not the most elegant solution but it’s also not a very common case because usually people need to re-render on prop change. If you intentionally want to wait for data to be fetched, longer term Suspense API will be a natural solution to that. But before you can use Suspense you’ll need components to be async-safe 🙂. |
I’d make the function memoized (i.e., add a cache to it) and just call it in const heavyComputation = _.memoize((value) => { ... });
const Component extends React.Component {
render() {
// Takes a while for the first call, just returns a cached result for next calls
const computedProp = heavyComputation(this.props.value);
return <div>{computedProp}</div>;
}
} |
Note you'd want to either scope memoization to a component instance or use some kind of LRU cache. |
Not 100% on topic but close to: toying around with the new lifecycle methods yesterday made me realize that it might in some cases force developers to break encapsulation of components since you have to "extract" helper functions which were, for the sake of convenience, class instance methods before and rewrite the way they work to a certain extent. Made up example, most simple use case I could think of:
I'd probably have to make that While I can understand the reasons behind making |
I think the notion that a method is more “encapsulated” than a function in the module scope is mistaken. If anything, a function declared in the module scope is more encapsulated because it’s not accessible by external code at all. Whereas a class method declared on a prototype (or even on the instance) is accessible by any external code that has an instance handle. If you insist on declaring all functions on the class (as opposed to putting them next to it in the same module) you can declare them as static. You can call static methods from instance methods so that lets you reuse the code. Although I’d argue that declaring such helpers outside is not just convenient but better from the encapsulation perspective. |
The main concern - sometimes you have to call javascript function, in response to props change. Usually it should not matter where/when you do it - before render, or after. Sometimes it could cause double renders -> render component cos prop changes and you have to render it to trigger didUpdate -> send an update to a thirdparty (the first time you have an instance) -> get response -> render everything again -> didUpdate again -> think how to prevent loop. Splitting component could help, but this is "more boilerplate code", yet again. This could be a mess. PS: A month ago I've spiked getDerivedStateFromProps+memoization, working perfectly in react-memoize and 16.3 redux spike(non official) |
Valid point. I will try to come up with a more realistic example when I'm home later today. Let's see. Maybe I only have to get used to that new |
Okay. After having written half of a novel I realized: everything I described could very likely be rewritten to use Found in general only 2-3 places where So nope, all fine 😉👍 |
That usage of state doesn't seem necessary. (Have you read this blog post, by chance?) I don't think you've really explained yet why this isn't sufficient: componentDidUpdate(prevProps) {
if (this.props.notificationData.notificationType !== prevProps.notificationData.notificationType) {
this.fetchData();
}
}
No, so long as the fetch in |
Hi @bvaughn, the first time notificationType changed because of a new push notification, the check When there is another push notification sent to the user, (and all subsequent push notifications from here) This is why I did the state based check. Hope it's clear. |
Hmm, I think I see, although I'm not sure how Could you compare |
Yes I can compare the notificationData.notificationTime now. Thats why I added notificationTime to redux now. notificationData gets created/recreated only when there's a new notification. And I can check noticationTime having changed by checking props and prevProps. Thank you @bvaughn |
@DeepaSriramRR As far as I understand, you want to trigger fetch whenever a user clicks the notification, even if Moreover, considering your new approach, What do you think, @bvaughn ? I wonder if my idea is good. :-) |
@tischsoic Thank you for the response. I use You are right about notificationTime not needed to be in state. I moved it to be a private property of the component. |
Hi guys, I have a new question related to this issue. I have started to migrate away from componentWillReceiveProps in a commercial project I am developing, and everything else seems to be going just great, but I have no idea how to solve a following type of situation (which there are a lot throughout the project). I have a React component, which is connected into a redux store (so a container component), and in this component I handle all the data changes etc. and re-render child components based on those changes. So my problem is like the following, I have a list of lets say buttons, and the buttons have certain data passed them as props, the data might change from time to time, and based on the changes I re-render them, I used to handle a lot of these kinds of re-creations of child components in componentWillReceiveProps, and now when I am changing it into getDerivedStateFromProps, I cannot bind my parent components functions into them anymore. So what would be a correct way of doing this kind of thing with the new static method (or some other React 16.4+ class method)
Should I just do this mapping in different method or what? I have tried to read comments in this post and blog posts, but I am still not fully comfortable of what to do. Can anyone help me with this? |
@flygis Is there a reason you're pushing elements into an array and not just putting them directly in your container component's render method? Something like: render() {
return (
<div>
{
this.props.buttons.map(button => (
<PunchButton
buttonText={button.buttonText}
handleButtonClick={this.handleButtonClick}
icon={icon}
id={button.id}
key={button.id}
returnTimeEnabled={button.returnTimeEnabled} />
))
}
</div>
)
} |
It's not obvious to me why you need to use either |
@Leeds-eBooks @bvaughn That is definitely an option, but if I remember correctly, I once read that there shouldn't be that much conditional rendering in the render method. And I have a lot of checks I need to do before I can render some of these elements. As I said this was just one example. But maybe I'll have to try to use the render method more to do the mapping, and somehow make the if statements separately in getDerivedStateFromProps or somewhere. Little bit off topic, but is it actually heavy for react to do some of these if else statements in the render method before the return? That could be a solution for me since I can access both the components internal state and the props there.. Just thinking out loud here. |
@flygis I'm sure @bvaughn will be able to correct me if I'm wrong, but the render method is exactly where you're expected to put conditional rendering. Don't be scared to question "advice" you read – including mine! – because there's a lot of stuff written about React which is overbearing, too concrete, or just plain wrong. If in doubt, check the React documentation (e.g. on conditional rendering). The fundamental point of the render method is to output UI based on state and props. If essentially what you're doing is checking conditions based on state and props, and deciding what to render – that is exactly what React is for and If your render method is starting to feel too heavy, don't just add methods – make new components! Split your big render method into smaller components, and either keep them in the same file or move them out into separate files, whatever works for you. If you're using React, you've already made the (excellent) decision to fully embrace components, so don't be frightened of them. As a rule of thumb, the more you use components over methods, the more you're playing the React game how it is supposed to be played and the more React will reward you with understandable, robust code that's easy to reason about. |
@Leeds-eBooks I actually found that page you mentioned before you added the comment, and started to move the conditional rendering into return function, by that I managed to almost completely get rid of the getDerivedStateFromProps. So thank you for the help! |
That's good to hear! 😄 @Leeds-eBooks's advice sounds solid. Conditional rendering is fine, and breaking up complex logic into separate React components can also be a good option in some cases. |
If there is a case of necessary to compare between this.props and nextProps, how can we do that something instead? each props variable that needed above cases, should managed as state? even it doesn't change depend on own component. hmmmm.. Is there a any way to use in this cases? |
I'm not sure I understand your question or use case @pyeongoh, but if you need to compare all props– you could put the entire props object in state, e.g. static getDerivedStateFromProps(nextProps, state) {
return {
prevProps: props
};
} |
If I understand it right componentWillRecieveProps is triggered only when props change and getDerivedStateFromProps is triggered by both props and state changing. There is a usecase when componentWillReceiveProps is doing its work fine and getDerivedStateFromProps won't be equivalent:
In this case if I replace componentWillReceiveProps with getDerivedStateFromProps it will be triggered by state change too (when calling onColumnToggle):
so state will be the same all the time because it will always take it from props. |
I still don't see any good solution for the problem. componentDidUpdate can't be considered as a replacement for componentWillReceiveProps because of duplicate rendering and getDerivedStateFromProps has no access to "this" and copying the previous props to state has many disadvantages:
|
The example code provided at the top of this issue, calling We've found that in most cases, |
@bvaughn: Sorry, my bad. After careful reading of the docs i understand the life cycle better and componentDidUpdate is what i need (data changes, API requests etc). Most of our components are fully controlled (or fully uncontrolled). There are perhaps some "problematic" use cases, but i don't see any right now. The lack of backward compatibility is still an issue and migration to the new life cycle will be a bit time-consuming, but i can live with it. |
(reactjs#721) * Sandpack error icon overlapping issue fix (reactjs#4302) * sandpack error icon overlapping issue fix * modified errorline css * Improve font display (reactjs#4308) * Generate Ids when there are none in local development (reactjs#4304) * Generate Ids when there are no headings * Tweak code Co-authored-by: Dan Abramov <[email protected]> * [Beta] useState Troubleshooting (reactjs#4309) * [Beta] useState Troubleshooting * Tweaks * tweak * docs: phrasing a sentence (reactjs#4185) * docs: phrasing a sentence (reactjs#4182) * docs: phrasing a sentence * Update extracting-state-logic-into-a-reducer.md Co-authored-by: dan <[email protected]> * docs: fix a grammatical error (reactjs#4183) Co-authored-by: dan <[email protected]> * Change "return statement" to "return keyword" (reactjs#4137) * small fixes to stopwatch codesandbox (reactjs#4110) * small fixes to stopwatch codesandbox noticed that the explanation for the first stopwatch codesandbox mentions "update the time every 10 milliseconds" so updated the codesandbox to reflect that also there's a small nuanced bug in the second stopwatch codesandbox where each call to `handleStart()` sets a new interval without checking if there's already one ongoing. Ie: If the user accidentally double clicks the start button, they set two intervals for updating `now` every 10ms and then intervalRef only retains the second interval ID. Thus, it's impossible to actually stop the timer because `handleStop()` will only clear the latest set interval while the original one will keep executing. * Update referencing-values-with-refs.md * Update referencing-values-with-refs.md * Update referencing-values-with-refs.md Co-authored-by: dan <[email protected]> * Resolve conflicts Co-authored-by: Amaresh S M <[email protected]> Co-authored-by: Sha Mwe La <[email protected]> Co-authored-by: Strek <[email protected]> Co-authored-by: Dan Abramov <[email protected]> Co-authored-by: Sofya Tuymedova <[email protected]> Co-authored-by: Soichiro Miki <[email protected]> Co-authored-by: Aayush Kumar <[email protected]> Co-authored-by: KnowsCount <[email protected]>
Hello
We have found a scenario where the removal of
componentWillReceiveProps
will encourage us to write worse code than we currently do.We currently consider
props
to be a valid form of input and state for our component. If a component received a prop, we do not duplicate it into our state as to maintain a single source of truth. This is especially true when using state management libraries where the state is externalized from the component but will also be true for many other scenarios.However, with the removal of
componentWillReceiveProps
, react will force us to usesetState
for any prop we would like to monitor for change. Basically, any type of prop change that would also trigger a following action (be it internal one or an external one) would require us to duplicate the prop into the state.To give a sort of clean example, let's imagine a dialog that has a
visible
flag and would like to report when it was hidden to a logging framework. Currently, we can do:With the new paradigm, I will need to do:
Aside from already duplicating the props, in my opinion this will heavily encourage users to do
Since it takes away a lot of the usefulness of props.
The text was updated successfully, but these errors were encountered: