A simple react component that invokes a promise from the data store on componentDidMount is throwing a warning:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the LocationNameView component.
I threw a few debug console.log to see if this.isMounted() is true/false, and inside componentDidMount this.isMounted() will return false the first time then again and it will be true. I'm not sure if the docs are clear or the name of componentDidMount is skewing my reasoning here but it seems like this method should only be invoked if the component is actually mounted.
enter link description here
componentDidMount: function() {
var self = this;
// make the request to the backend and replace the loading location text
Models.Location.find(this.props.location)
.then(function(location) {
console.log(self.isMounted()); // <--- shows false then true
self.setState({ location : location });
});
},
componentDidMount is invoked when the underlying DOM representation is built, but, not yet mounted to the actual DOM.
The reason the warning is being displayed about setting state on an unMounted component is because the aSync callback in the above example can return before the component is actually mounted to the DOM tree in the client/browser.
The lesson here is if you're using an aSync callback that sets state in your component on componentWillMount or componentDidMount, first use the safety catch isMounted() before proceeding to setting state, ie:
componentDidMount() {
let self = this;
PromiseMethod().then(function aSyncCallback(data) {
if ( self.isMounted() ) {
self.setState({...data});
}
});
}
React Component Lifecycle reference
Related
I have the following two components.
<Foo>
<Bars/>
<Foo>
When Foo is called the console shows:
call Bar useEffect
Warning: Cant perform a React state update on an unmounted component...
call Foo useEffect
call Bar useEffect again
Here's the codepen but it doesn't generate the same warning which could be because its in development mode on my local. It does show null prints though.
https://codepen.io/rsgmon/pen/OJmPGpa
Is this component structure, with useEffect in each component, not recommended/allowed?
If it's ok what do I need to change to avoid the warning?
This is likely occurring because a state update to Foo triggers a re-render, causing Bars to unmount and remount.
Then the original Bars API request completes and it attempts to update state on the component that isn't mounted anymore, hence the warning.
You can fix it by declaring a variable inside the effect that's initially false and setting it to true in the effect's cleanup function:
useEffect(() => {
let unmounted = false;
doSomeAsynchronousStuff()
.then( result => {
if (unmounted) {
return; // not mounted anymore. bail.
}
// do stuff with result
})
return () => unmounted = true; // cleanup gets called on un-mount
}, [])
How do I use Async Await ?
When this.props.onChangeStep1() is called, the component where the view is rendered is replaced by another component.
If the component has been swapped would Async Await still work ?
s3.upload(params, function(err, data) {
this.props.onChangeStep1(); //<----- After s3.upload I want to call this first
this.setState( //<----But I want this to run in the background
// even though the component is not in view
{
myState: "data-from-s3Upload"
});
}.bind(this)
);
If a callback is set to be executed later and the component that initiated the asynchronous call is unmounted, the callback will be executed regardless. If the callback attempts to act on the unmounted component (changing its state for instance) it will be considered as a memory leak and React will notify you with an error message in the console.
this.props.onChangeStep1(e) // <-- Forward data and Process logic on App.js / Parent Component
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.
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.
When a UI event occurs, my top-level React component receives data to use as props from an external object. I'd like to know the correct way to update the component's props to use for the next render.
It seems like one of the component lifecycle methods should handle this, but that doesn't seem to be the case.
The code below shows what I've tried:
• Root.update: Custom method invoked externally once the data is ready. Both of the two techniques shown do work.
• Root.getDefaultProps: Used to retrieve props for first render.
• Root.render: This also works, but is redundant on first render.
• Root.componentWillUpdate: Does not work but seems like it should.
• Root.componentWillReceiveProps: Wouldn't make sense for this to work; props aren't received from a React component.
var Root = React.createClass({
update: function() {
this.setProps(Obj.data); // works but setProps is deprecated
this.props = Obj.data; // always works
},
getDefaultProps: function() {
return Obj.pageload(); // works on first render
},
componentWillUpdate: function() {
this.props = Obj.data. // does not work
this.setProps(Obj.data); // infinite loop
},
componentWillReceiveProps: function() {
this.props = Obj.data; // does not work
},
render: function() {
this.props = Obj.data; // works but is redundant
// ...
},
});
At some point you must be calling React.render to render that initial root component with your props.
To update the component with new props simply call React.render again for the same component on the same Dom element with the new props.
This won't re-mount the component but will in fact simply send the mounted instance your new props and cause it to re-render with them.
If you want to control what happens when your component receives props then take a look at the component lifecycle method componentWillReceiveProps.