I have been programming with React for a while now but I have never faced this annoying issue, in one of my components componentWillReceiveProps fires before setState() in componentDidMount gets executed. This causes several issues in my application.
I have a variable this.props.flag received from props which is going to be stored in the state of the component:
componentDidMount() {
if (!_.isEmpty(this.props.flag)) {
console.log('Flag Did:', this.props.flag);
this.setState({
flag: this.props.flag
},
() => doSomething()
);
}
In my componentWillReceiveProps method the variable this.state.flag is going to be replaced just if it is empty or if it different from the value of this.props.flag (the checks are made by using the lodash library):
componentWillReceiveProps(nextProps) {
const { flag } = this.state;
console.log('Flag Will:', !_.isEqual(flag, nextProps.flag), flag, nextProps.flag);
if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) {
this.setState({
flag: nextProps.flag,
},
() => doSomething()
);
}
}
Suppose that the prop flag in this case has always the same value and that this.state.flag is initialized to undefined. When I check the console log I see the following result:
Flag Did: true
Flag Will: true undefined true
Therefore when the code enters componentWillReceiveProps the value of this.state.flagis still undefined, that means has not been set yet by the setState in componentDidMount.
This is not consistent with React lifecycle or am I missing something? How can I avoid such behaviour?
ComponentWillReceiveProps() will be called in each update life-cycle caused by changes to props (parent component re-rendering). Since Javascript is synchronous you might have validate props sometimes to save app crashes. I've not totally understood the context of your app but what you can do is:
componentWillReceiveProps(nextProps) {
const { flag } = this.state;
if(!flag){
return;,
}
console.log('Flag Will:', !_.isEqual(flag, nextProps.flag), flag, nextProps.flag);
if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) {
this.setState({
flag: nextProps.flag,
},
() => doSomething()
);
}
}
You can return if state is undefined. It will be called again upon parent re-rendering. But this might not be use-case.
Anyways you should look into this:
But I can think of at least 1 (maybe theoretical) scenario where the order will reversed:
Component receives props, and starts rendering. While component is
rendering, but has not yet finished rendering, component receives new
props. componentWillReceiveProps() is fired, (but componentDidMount
has not yet fired) After all children and component itself have
finished rendering, componentDidMount() will fire. So
componentDidMount() is not a good place to initialise
component-variables like your { foo: 'bar' }. componentWillMount()
would be a better lifecycle event. However, I would discourage any use
of component-wide variables inside react components, and stick to
design principles:
all component variables should live in either state or props (and be
immutable) all other variables are bound by the lifecycle method (and
not beyond that)
As suggested by user JJJ, given the asynchronous nature of setState, the check if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) in componentWillReceiveProps is executed before setState inside componentDidMount executes flag: this.props.flag. The order of the operations is:
Code enters componentDidMount.
Code executes setState in
componentDidMount (flag: this.props.flag hasn't happened yet).
Code exits componentDidMount, setState in componentDidMount is
still under execution (flag: this.props.flag hasn't happened yet).
Component receive new props, therefore enters
componentWillReceiveProps.
The statement if
(!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) in
componentWillReceiveProps is executed (this.state.flag is still
undefined).
Code finishes the execution of setState inside
componentDidMount and sets flag: this.props.flag and executes
doSomething().
Code finishes the execution of setState inside
componentWillMount and sets flag: nextProps.flag and executes
doSomething().
Given the asynchronous nature of setState, 6 and 7 could be executed in parallel and therefore we do not know which one will finish its execution first. DoSomething() in this case is potentially called at least 2 times when it must be called once instead.
In order to solve these issues, I changed my code this way:
componentWillReceiveProps(nextProps) {
if (!_.isEmpty(nextProps.flag) && !_.isEqual(this.props.flag, nextProps.flag)) {
this.setState({
flag: nextProps.flag,
},
() => doSomething()
);
}
}
This way I compare the new version(nextProps) with the old version(this.props) of the props, without waiting for the flag value to be stored in the component's state.
Related
This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 7 months ago.
I'm trying to learn react and in doing so i need to do a lot of console.log(something). In the example below, could you please explain why the second console.log(this.state.foo);, the one within componentDidMount(), is not true? Thanks in advance.
Note: please don't suggest using functional components or hooks, the repo i will be working on is all class based components, so that's what i'm focusing on for now.
class App extends React.Component {
constructor (props){
super(props);
this.state = {
foo: false
}
console.log(this.state.foo);
}
componentDidMount() {
console.log('componentDidMount() lifecycle');
// Trigger update
this.setState({ foo: true });
// Why won't this one show in the console?
console.log(this.state.foo);
}
render() {
console.log('Render lifecycle')
return(
<h1>Hellos</h1>
)
}
}
ReactDOM.render( < App />, document.getElementById('container'));
That console.log is logging, but it's still logging false for the value this.state.foo. The reason for this is that setState is actually an asynchronous function (in other words, it takes time to execute completely), and you cannot expect the state update operation to have succeeded before you try to access the new value for state.
componentDidMount() {
console.log("componentDidMount() lifecycle");
// Updating state is asynchronous
this.setState({ foo: true });
// The line below is running, but the previous line updating state
// has not finished executing, which is why it's still logging false
console.log("component did mount", this.state.foo);
// => "component did mount", false
}
Here are the React docs on the topic.
As per docs: https://reactjs.org/docs/react-component.html#setstate
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.
Your
// Why won't this one show in the console?
console.log(this.state.foo);
Works and shows initial false value, that is actually expected. It will be updated on next shouldComponentUpdate call.
I am checking a condition and if that condition holds true then through function a want to change the state. i tries but i console it is still showing false only. it will be really helpful if you suggest good way to to it,
this.state = {
popUpStatus:false,
}
handlePopUp = () => {
this.setState({
popUpStatus: true,
});
}
if(a > 100){
this.handlePopUp;
console.log(popUpStatus)l // false
}
this.setState() is an asynchronous call. Therefore, You should not check this.state.popUpStat value right after calling this.setState().
The correct way to do so is passing a callback function as a second parameter in this.setState() call like
this.setState({
popUpStatus: true,
},()=>console.log(this.state.popUpStatus))
setState function does not update the state immediately. It just enqueues the changes. popUpStatus will have a different value once the component is re-rendered.
"setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. Think of setState() as a request rather than an immediate command to update the component....."
You can read more on setState here https://reactjs.org/docs/react-component.html#setstate
To test it, you can log it in your render function, and check how it updates.
You have to display this.state.popUpStatus
Replace console.log(popUpStatus) with console.log(this.state.popUpStatus)
Try to do this:
this.handlePopUp();
console.log(this.state.popUpStatus);
But consider that setState is async function so you have to do this:
this.setState({
popUpStatus: true,
}, ()=> { console.log(this.state.popUpStatus); });
Perhaps with hooks should be better:
useEffect(()=> { setPopUpStatus(a>100); }, [a])
As far as I could see, the only thing a componentWillMount can do and a constructor cannot is to call setState.
componentWillMount() {
setState({ isLoaded: false });
}
Since we have not called render yet, a setState in componentWillMount will prepare the state object before we enter the first render() pass. Which is essentially the same thing a constructor does:
constructor(props) {
super(props);
this.state = { isLoaded: false };
}
But I see another use case where componentWillMount is useful (on server side).
Let's consider something asynchronous:
componentWillMount() {
myAsyncMethod(params, (result) => {
this.setState({ data: result });
})
}
Here we cannot use the constructor as assignment to this.state won't trigger render().
What about setState in componentWillMount? According to React docs:
componentWillMount() is invoked immediately before mounting occurs. It
is called before render(), therefore setting state in this method will
not trigger a re-rendering. Avoid introducing any side-effects or
subscriptions in this method.
So, here I think React will use the new state value for the first render and avoids a re-render.
Question 1: Does this means, inside componentWillMount, if we call setState in an async method's callback (can be a promise callback), React blocks initial rendering until the callback is executed?
Having this setup on client-side (yes I see that use case in server-side rendering), if I assume the above is true, I will not see anything until my asynchronous method completes.
Am I missing any concepts?
Question 2: Are the any other use cases that I can achieve with componentWillMount only, but not using the constructor and componentDidMount?
Does this means, inside componentWillMount, if we call setState in an
async method's callback (can be a promise callback), React blocks
initial rendering until the callback is executed?
No, see here.
The following code doesn't block render (bear in mind this would be an anti pattern anyways to call setState there)
componentWillMount: function() {
new Promise((resolve, reject) => {
setTimeout(()=> {
resolve();
}, 2000)
}).then(() => this.setState({ promiseResult: 'World' }));
},
Question 2: Are the any other use cases that I can achieve with
componentWillMount only, but not using the constructor and
componentDidMount?
No, for ES6 classes you can discard componentWillMount. It is only needed if you use React.createClass({... })
EDIT: Apparently, I'm wrong. Thanks to #Swapnil for pointing this out. Here is the discussion.
React throws a warning if there is a side effect in the constructor which modifies state in another component, because it assumes that setState in the constructor itself and potentially during render() is being called. So no side effects in the constructor are desired.
This is not the case if you do it in componentWillMount, no errors are thrown. On the other hand, the guys from facebook discourage side effects in componentWillMount also. So if you don't have any side effects, you could use the constructor instead of componentWillMount. For side effects it is recommended to use componentDidMount instead of componentWillMount.
Either way, you don't need componentWillMount.
I have a login page in which I am using componentWillReceiveProps to route to the next page. But the state that I am setting inside componentWillReceiveProps does not seem to set.
This is my componentWillReceiveProps method :
componentWillReceiveProps(nextProps) {
if (nextProps.isAuthenticated === true) {
browserHistory.push('/home');
} else {
console.log("this.props :::" + JSON.stringify(this.props))
console.log("this.state :::" + JSON.stringify(this.state))
console.log("nextProps :::" + JSON.stringify(nextProps))
this.setState({
errorMessage: nextProps.authenticationError
})
console.log("this.state :::" + JSON.stringify(this.state))
}
}
The console output I am getting is this :
this.props :::{"authenticationError":null}
this.state :::{"username":"35135","password":"3135","errorMessage":""}
nextProps :::{"isAuthenticated":false,"authenticationError":"Could not find user in DB."}
this.state :::{"username":"35135","password":"3135","errorMessage":""}
Here even after setting the state, my state has not changed.
Please tell me what is it that I'm doing wrong.
EDIT: I have this component which is ErrorText , which takes in the errroMessage property.
<ErrorText errorMsg={this.state.errorMessage}></ErrorText>
setState() is an asynchronous operation, so it doesn't take effect immediately:
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state [...]
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback [...], either of which are guaranteed to fire after the update has been applied.
Here's an example of a setState callback in your context:
this.setState(
{ errorMessage: nextProps.authenticationError },
function() {
console.log( 'this.state ::: ' + JSON.stringify( this.state ) );
}
);
Refer to same stackoverflow question:
Why Calling react setState method doesnt mutate the state immediately
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
This is likely because this.setState is an async function. You can pass a callback to handle events which should happen directly after setting state. Checkout this chosen answer for a code example.
I have several React containers and components running perfectly, but I'm still studying and learning React. When my function componentDidMount runs in a new component I'm working on, it appears that all the data my component needs is available to the component, but this.props.loading is set to true;
componentDidMount() {
const a = 100; //breakpoint here
if (this.props.loading === false){
const {myDataID} = this.props;
this.fromID = Meteor.userId();
this.toID = myDataID;
this.subscribe(fromID, toID);
}
}
What controls the value in this.props.loading? Will componentDidMount run again if this.props.loading is set to false?
The prop, this.props.loading is set by the parent component. If you feel your component has all the necessary data to perform a render you don't need to check if this.props.loading is true or false. It's a good practice to have this check because some times due to network errors you might not get the data you needed. This will break your code.
componentDidMount will only be called once after the render function is done
Invoked once, both on the client and server, immediately before the initial rendering occurs. If you call setState within this method, render() will see the updated state and will be executed only once despite the state change.
But, when you change the loading prop in the parent, componentWillReceiveProps will be called with the nexProp, which is your change.
For more info, check here
What controls the value in this.props.loading?
The component that is rendering that component. I.e. if this componentDidMount method is in a component Bar, and in Foo's render method:
render() {
return <Bar loading={computeLoadingState()} />
}
Every time Foo is rerendered, Bar is potentially passed new value for loading.
Will componentDidMount run again if this.props.loading is set to false?
No. The documentation says:
Invoked once, only on the client (not on the server), immediately after the initial rendering occurs.
Other methods however are invoked whenever props change.