Федор Усаков Федор Усаков - 23 days ago 12
React JSX Question

React Native, "setState() on unmounted component" warning

I have a working piece of code that triggers warning (the code should login to the REST and turn off Activity Indicator). I don't understand how my component could be unmounted as I bind onLoginPressed on button tap, after all possible mounts should already be finished. Please help me to get rid of it and understand the cause of the warning...

UPD: Thanks to the comments I figured out that component is unmounted and componentWillUnmount is invoked... But I don't understand why and when it is happening...


"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 undefined
component."


Here is my code, warning is triggered by second
setState
:

onLoginPressed(){
console.log('Attempted to login with: '+this.state.username);
this.setState({showProgress: true});

AuthService.login({
login: this.state.username,
password: this.state.password
}, (results)=> {
this.setState(Object.assign({ // have to create a solid Object with all states to be changed at once
showProgress: false // turning off ActivityIndicator
}, results));

if(results.success && this.props.onLogin){
this.props.onLogin(); // Yey! We logged in! Let's move to the next View
}
});
}


...
AuthService.js:

login (creds, callback) {
fetch(CFG.AUTH_URL)
.then((response)=> {
if((response.status >= 200) && (response.status < 300)){
return response.json();
}
throw {
serverError: (response.status == 500) || (response.status == 501),
unknownError: (response.status != 500) || (response.status != 501)
};
})
.then((result)=> {
AsyncStorage.multiSet([ // Have to save login+password and session_hash for later use...
[authKey, JSON.stringify(creds)],
[sessionKey, result.data.session_hash]
], (err)=> {
if (err) throw err;
});
return callback({success: true});
}
})
.catch((err)=> {
return callback(err);
});
}// end of login

Answer

Федор Усаков, I ran into this problem as well. I found that the component was re-rendering due to setting the state in the callback function of AuthService.login(). The issue (in this situation) with setState:

There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

from React docs

My guess is that the state is not being set immediately and then this.props.onLogin() is called. This unmmounts the component before the state could be set, and voila, warning.

To get mine to work I removed the conditional and the call to onLogin() and added a shouldComponentUpdate() method to the Login class. In the shouldComponentUpdate() I placed the removed conditional and call. Like so:

onLoginButtonPress() {
    // some code

    AuthService.login({
        username: this.state.username,
        password: this.state.password
    }, (results) => {
        this.setState({
            ...results,
            showProgress: false
        });
    });
}

shouldComponentUpdate() {
    if (this.state.loginSuccess && this.props.onLogin) {
        this.props.onLogin();
    }
    return true;
}

This ensures the state has been set, the component then re-renders and because this.state.loginSuccess is now true, call this.props.onLogin() which causes Login to unmount.