I've read the documentation on reactjs.org regarding getDerivedStateFromProps. I've seen the use cases. And I understand why to use it.
But I cannot figure out how it deals with the return value. Hence my question, when it does return... where does it go?
It doesn't setState because it does not have access to this.
To set state, requires using componentDidUpdate and looking for changes, then setting state.
Assume the following code:
public static getDerivedStateFromProps: IArrowFunction = (nextProps: IMyContainerProps, prevState: IMyContainerState) => {
if (nextProps.havingFun !== prevState.havingFun) {
console.log('[MyContainer.tsx] getDerivedStateFromProps :::::CHANGED::::::', nextProps, prevState);
return { havingFun: nextProps.havingFun };
} else { return null; }
}
So what does React do with that return ^?
I could then setState on componentDidMount. But this does not appear to have anything to do with getDerivedStateFromProps. Additionally this lifecycle method triggers every time. And it forces a re-render.
public componentDidUpdate(prevProps: IMyContainerProps, prevState: IMyContainerState): void {
if (prevState.havingFun !== this.props.havingFun) {
console.log('[MyContainer.tsx] componentDidUpdate *******CHANGED******', prevState, prevProps, this.props);
this.setState({ havingFun: this.props.havingFun });
}
}
At what point does the return from getDerivedStateFromProps come into play?
UPDATED: this lifecycle method will actually update state if properly configured as above. You do not need an additional method to setState
The return value is similar in concept like setState:
return { havingFun: nextProps.havingFun };
Where you can assume havingFun a state property and the value nextProps.havingFun is updated value.
When you use return null it means you are not doing anything with the state.
Note: getDerivedStateFromProps is really different concept than setState though I have tried here to explain it the way only.
You should read the docs for more detail:
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
It's returned to state when the parent component re-renders.
Related
I have my ClockBlock component where I lifted state keeped in object "timer":
class ClockBlock extends Component {
constructor(props){
super(props);
this.state = { timer: props.timer }; // timer from Redux store
}
render(){
return(
<div className="clockBlock">
<Circle timer={this.state.timer} updateTimer={this.updateTimer.bind(this)} />
<Clock timer={this.state.timer} updateTimer={this.updateTimer.bind(this)} />
<ClockTerms updateTimer={this.updateTimer.bind(this)} />
</div>
)
}
All the three nested component influence each other via updateTimer function. (except ClockTerms component - it works in one direction). The function is here:
updateTimer(newDuration){
const newState = Object.assign( {}, this.state );
newState.timer.duration = newDuration;
this.setState( newState );
}
So, the problem - when I change timer using ClockTerms component I see changes in the Clock component too (via props, apparently). In the same time in the Circle component in shouldComponentUpdate function i'm trying to see the difference between the old props and the new. Here is this function:
shouldComponentUpdate(nextProps, nextState){
console.log( this.state.timer );
console.log( nextState.timer );
console.log( this.props.timer );
console.log( nextProps.timer );
return true;
}
All the console.log() calls print the same data - new props. Why? And how can I get old props?
PS: i simplified my code above, removing irrelevant from my point of view calculations. Can give whole code, if it is important.
I also use Redux here, but it seems to me it isn't engaged here much.
Great thanks in advance!
UPDATE: I also get the same picture when place the same shouldComponentUpdate function in ClockBlock (parent) component;
You can functionally set state. Because setState is async, when multiple things call setState React uses the most recent properties passed to setState.
Instead you can update state by passing an updater function. This batches all the state updates. When Reacts lifecycle begins to update state it'll process all the pending updater functions in sequence.
This is from the docs describing what happens when setState uses an object.
Subsequent calls will override values from previous calls in the same
cycle, so the quantity will only be incremented once. If the next
state depends on the current state, we recommend using the updater
function form, instead:
this.setState((state) => {
return {quantity: state.quantity + 1};
});
https://reactjs.org/docs/react-component.html#setstate
I've solved a similar situation by comparing this.state to nextProps and updating state. I don't think it's a great solution, but didn't come up with anything else jet.
state = {
dayOff: this.props.myProperty
}
shouldComponentUpdate(nextProps, nextState) {
const shouldUpdate = this.state.myProperty !== nextProps.myProperty;
if (shouldUpdate) this.state.myProperty = nextProps.myProperty
return shouldUpdate;
}
The point is that updateTimer fires BEFORE shouldComponentUpdate runs. That's it. So, when you update your lifted state in the parent component from children components (via passed in props function), keep in mind that shouldComponentUpdate will get already changed state and props.
componentDidUpdate(){
var date = this.props.navigation.state.params.selected_date
this.setState({
sleepinputs_date: date
})
}
When I try to setState the props value it throws an error "
You had created infinite state update.
Inside componentDidUpdate you update the state, when it updates the state, componentDidUpdate invokes again in this stuff keeps going without ending.
According to react docs, you will get an argument in componentDidUpdate. You can set state only if you use a conditional like the following.
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
Basically you are comparing the old props with the new ones. Only if they are different, you can keep updating or modifying the value. If previous props are the same as current props, why you botter to set state them again.
Before upgrading react to version 16.3, I'd call a method based on changes in props like this :
componentWillReceiveProps(nextProps){
if(this.props.country.length !== nextProps.country){
doSomething(); //example calling redux action
}
}
componentWillReceiveProps is unsafe on Version 16.3 and we must use getDerivedStateFromProps. However, this method returns an object and I don't know how I can call a method from inside it the same way I do with componentWillReceiveProps
Yes, you need to return an object, which is the new state that that is derived from nextProp. According to docs:
getDerivedStateFromProps should return an object to update state, or null to indicate that the new props do not require any state updates.
But since you are not updating your state in any way inside your componentWillReceiveProps, you should use componentDidUpdate instead of getDerivedStateFromProps:
componentDidUpdate(prevProps){
if ( prevProps.country !== this.props.country.length ) {
doSomething(); //example calling redux action
}
}
For this situation, it was good for the OP to use componentDidUpdate but I found myself needing getDerivedStateFromProps so I had to make my custom function static as well and call it using the class' name inside getDerivedStateFromProps. Something like this:
componentDidMount() {
const something = ClassComponentName.runThisFunction();
this.setState({ updatedSomething: something });
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.key !== prevState.key) {
return {
updatedSomething: ClassComponentName.runThisFunction()
};
}
return null;
}
static runThisFunction() {
//do stuff and return value
}
To clarify, this is updating the component's state on load as well as when new props arrive. This definitely took me back to my typed-language days. Hope it helps!
if you need to call a function in "getDerivedStateFromProps", you can put this function in state in constructor , then get this function in "getDerivedStateFromProps" from state.
put function in state in constructor:
constructor(props){
super(props);
this.state = {
func1:this.func1.bind(this)
}
}
get function from state in getDerivedStateFromProps:
getDerivedStateFromProps(props,state){
return {
model:state.func1(props.model)
}
}
Insdead of using setState in componentWillReceiveProps, I just set the props to component's local property directly:
class Inventory extends React.PureComponent {
pieData = {};
doSomeTransition = (pieData) => {
};
componentWillReceiveProps(nextProps) {
if (this.props.productionOverview !== nextProps.productionOverview) {
this.pieData = this.doSomeTransition(nextProps.productionOverview);
}
}
render() {
return (
<Production chartData={this.pieData} />
);
}
}
The <Production /> component re-render well as every time props. productionOverviewchanges.
I feel like this is a wrong way but I can't tell why, because all the components work well as expected.
componentWillReceiveProps is usually used to update your React component state when the props have changed. Which is why you will usually see setState inside that function.
That being said there is nothing wrong in calling something else, it all depends on the type of operation you are actually doing inside doSomeTransition ...
I don't believe you will experience any obvious side effects but you will not have any guarantees on when the property's value is actually updated/modified whereas utilizing the this.state you know the state is being handled in accordance to the state's lifecycle.
My React component sets a state property called userInGroup based on props.
this.state = {
userInGroup: this.props.user
? this.props.user.groups.includes(props.group._id)
: false,
};
This works but does not update when the props change and the value of userInGroup should also change, until I refresh the page. How can I make this update reactively?
Maybe I could use componentWillUpdate or componentDidUpdate but then Id be repeating the logic used by userInGroup. Is this repetition inevitable?
You need to use getDerivedStateFromProps. The other methods are now deprecated and deemed unsafe.
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
This method exists for rare use cases where the state depends on changes in props over time. For example, it might be handy for implementing a component that compares its previous and next children to decide which of them to animate in and out.
static getDerivedStateFromProps(props) {
return {
userInGroup: props.user
? props.user.groups.includes(props.group._id)
: false,
}
}
Yes you need to make use of componentWillReceiveProps along with constructor/componentDidMount, when you want to update state when props change since constructor/componentDidMount are called only once when the component mounts, and componentWillReceiveProps() is invoked before a mounted component receives new props or the Parent component updates
You could write a function that contains the logic
this.state = {
userInGroup: this.getUserStatus();
};
componentWillReceiveProps(nextProps) {
if(nextProps.user !== this.props.user) {
this.setState({userInGroup: this.getUserStatus(nextProps)})
}
}
getUserStatus = (newProps) => {
const data = newProps || this.props
return data.user
? data.user.groups.includes(data.group._id)
: false
}