7

I am fetching data in componentDidMount and updating the state and the famous warning is appearing:

Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

My code is as follow:

componentDidMount() {
    let self = this;

    let apiBaseUrl = Config.serverUrl;
    axios.get( apiBaseUrl + '/dataToBeFetched/' )
        .then( function(response) {
            self.setState( { data: response.data } );;
        } );
}

What is causing this warning and what is the best way to fetch the data and update the state?

O_k
  • 993
  • 1
  • 8
  • 13

4 Answers4

7

Based on a previous answer, I have done the following which worked fine:

constructor(props) {
   this.state = {isMounted: false}
}

componentDidMount() {
    let apiBaseUrl = Config.serverUrl;
    this.setState( { isMounted: true }, () => {
        axios.get( apiBaseUrl + '/dataToBeFetched/' )
            .then( (response) => { // using arrow function ES6
                if( this.state.isMounted ) {
                    this.setState( { pets: response.data } );
                }
            } ).catch( error => {
                // handle error
        } )
    } );
}

componentWillUnmount() {
    this.setState( { isMounted: false } )
}

Another better solution is to cancel the request in the unmount as follows:

constructor(props) {
    this._source = axios.CancelToken.source();
}

componentDidMount() {
    let apiBaseUrl = Config.serverUrl;
    axios.get( apiBaseUrl + '/dataToBeFetched/', { cancelToken: this._source.token } )
        .then( (response) => { // using arrow function ES6
            if( this.state.isMounted ) {
                this.setState( { pets: response.data } );
            }
        } ).catch( error => {
            // handle error
    } );
}

componentWillUnmount() {
    this._source.cancel( 'Operation canceled due component being unmounted.' )
}
O_k
  • 993
  • 1
  • 8
  • 13
  • You didn't explain the cause of the error. Is it that the component is mounting and then subsequently un-mounting before the async get call and setState calls finish? – JoeTidee Aug 26 '18 at 21:52
  • Yes, that's right. The unmounting doesn't wait for the async call to finish. Eventually when the async call gets over, the component is already unmounted and so it cannot set the state and you wouldn't want to do that anyways. To do this, the method in the answer above simply does a check to see if the component is mounted before setting the state. – Anjan Biswas Oct 13 '18 at 18:29
  • @O_k Thanks! why the need to do the AJAX inside the this.setState - i.e. this.setState( { isMounted: true }, () => { ... ? – dowi Dec 09 '19 at 10:13
  • 1
    @dowi this is to make sure that isMounted is set before you do the axios call. Using setState does not change the state immediately. – O_k Dec 22 '19 at 03:27
4

You can try this:

constructor() {
    super();
    this._isMounted = false;
}

componentDidMount() {
    this._isMounted = true;
    let apiBaseUrl = Config.serverUrl;
    this.setState( { isMounted: true }, () => {
        axios.get( apiBaseUrl + '/dataToBeFetched/' )
            .then( (response) => { // using arrow function ES6
                if( this._isMounted ) {
                    this.setState( { pets: response.data } );
                }
            } ).catch( error => {
                // handle error
        } )
    } );
}

componentWillUnmount() {
    this._isMounted = false; // equals, not :
}
Henry Woody
  • 14,024
  • 7
  • 39
  • 56
  • Can you explain which part of "this" makes the difference? You should do so such that others can learn from your answer – Nico Haase Feb 05 '19 at 13:11
  • no need to set state, this code also works without setting state of isMounted. –  Feb 05 '19 at 13:16
1

This most likely happened because the component was already unmounted before the async call finished. Meaning, your call to setState in the axios promise is being called after the component is already unmounted, perhaps because of a react-router Redirect or a change in state?

alexlod
  • 1,363
  • 2
  • 10
  • 18
0

Call setState on componentWillUnmount is the worst practice

componentWillUnmount() {
    this.setState( { isMounted: false } ) // don't do this
}
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Shawn Wang
  • 2,314
  • 1
  • 16
  • 19