React memory leak, componentDidUnmount not working

Viewed 2911

I have reviewed many posts attempting to resolve this issue and have tried numerous ways to take care of this error, but the answer stills eludes me. It is a common problem, I know, but my understanding of React appears to be inadequate to achieve resolution. I'm trying to eliminate this error:

Can't perform a React state update 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.

I am unable to understand why componentWillUnmount does not resolve it. Here is the code:

import React from 'react';
import ReactDOM from 'react-dom';

export class PracticeData extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      data: [],
      isLoaded: false,
    }
}
componentDidMount() {
  this.mounted=true;
  fetch('https://jsonplaceholder.typicode.com/users')
  .then(res => res.json())
  .then(json => {
    this.setState({   //triggers a rendering cycle
      isLoaded: true,
      data: json,
    })
  });
} 
componentWillUnmount(){
  this.isLoaded=false;
}
  render(){
    var { isLoaded, data } = this.state;
      if(!isLoaded) {
        return <div>Loading...</div>
      } else {
       return (
         <div className="container-fluid">
           <div className="row">{
             data.map(data => <div key={data.id}>
               <div className="list">
                 <li className="header">{data.name}</li>
               <div className="body">  
                 <li><strong>Company:</strong> {data.company.name}</li>
                 <li><strong>User Name: </strong> {data.username}</li>
                 <li><strong>Email: </strong> {data.email}</li>
                 <li><strong>Phone: </strong>{data.phone}</li>
               </div>
                 <li className="address"><strong>Address: </strong></li>
               <div className="body">
                 <li><strong>Street:</strong> {data.address.street}</li>
                 <li><strong>Suite: </strong> {data.address.suite}</li>
                 <li><strong>City: </strong> {data.address.city}<br/></li>
                 <li><strong>Zip Code:</strong> {data.address.zipcode}</li>
               </div>   
               </div>
               </div>
              )}
           </div>
          </div>
        );
      }
    }
  }
ReactDOM.render(<PracticeData/>, document.getElementById('root'));
2 Answers

The fetch looks like the only thing that can possibly resolve after a component unmounts, and for this you'll need an AbortController.

Basic usage is

const controller = new AbortController();
const { signal } = controller;

fetch(url, { signal })

Later if needed, you can abort the fetch

controller.abort()

Update to your component

export class PracticeData extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      data: [],
      isLoaded: false,
    }
  }

  controller = new AbortController();

  componentDidMount() {
    this.mounted=true;
    const { signal } = this.controller;

    fetch('https://jsonplaceholder.typicode.com/users', { signal }) // <-- pass signal in fetch options
      .then(res => res.json())
      .then(json => {
        this.setState({
          isLoaded: true,
          data: json,
        })
      })
      // catch any rejected fetch promises (including aborted fetches!)
      .catch(console.error);
  }

  componentWillUnmount(){
    this.isLoaded=false;
    this.controller.abort(); // <-- abort any in-flight fetch requests
  }
...

Edit cancel fetch request on unmount

NOTE: fetch normally only returns rejected Promises for network errors, valis HTML error responses will return in resolved Promises. As noted in comment, aborted fetches will also return a rejection.

According to official React docs, life cycle method componentWillUnmount should be used for a set of scenarios, basically to do any kind of cleanup.

What does cleanup mean, it is nothing but canceling any event listeners you may have added for any element rendered by the current component, or maybe you initialized a subscription in your componentDidMount method. Or a network call that you may have initialized.

In a nutshell, remove, cancel, or clean any kind of service which was started by this component during its lifetime so far.

What you should never do in a componentWillUnmount method is to set the local state. As this setState will trigger a render, and this render will never be actioned, Why ?

Because once a component is unmounted/destroyed. It would have no effect of that setState called in componentWillUnmount. Moreover this setState that you are trying to call from componentWillUnmount is what React refers to as the memory leaks.

For more in depth information, I would suggest reading the official doc here

Related