Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

componentDidMount inside a class causing a memory leak #14630

Closed
dorianrod opened this issue Jan 18, 2019 · 5 comments
Closed

componentDidMount inside a class causing a memory leak #14630

dorianrod opened this issue Jan 18, 2019 · 5 comments
Labels
Resolution: Stale Automatically closed due to inactivity Type: Question

Comments

@dorianrod
Copy link

dorianrod commented Jan 18, 2019

Hi,

I have a very weird memory leak that seems to be related to componentDidMount declaration. Memory is not freed after unmounting component.

Used code

Version: react 16.7
Mode: developper or production (same behaviour)

Here is the code I use to hide or display a list of items

class Item extends React.Component {
     componentDidMount() {

    }
      render() {
            return <div>test item</div>;
      }
}

class Items extends React.Component {
      constructor(props) {
            super(props);
            this.state =  {};
      }

      renderList() {
            let items = [];
            for(var i = 0; i < 4000; i++) {
                  items.push( <Item key={i} />);
            };

            return items;
      }

      onDisplay = ()=>{
            this.setState({display: true});
      }
      onHide = ()=>{
            this.setState({display: false});
      }

      render() {      
            return <div>
                  <div key="display" onClick={this.onDisplay}>Display</div>
                  <div key="hide" onClick={this.onHide}>Hide</div>
                  {this.state.display ? this.renderList() : null}
            </div>
      }
}
ReactDOM.render(<Items />, document.getElementById("app"));

Steps to reproduce

CASE 1
Use google chrome and display the performance monitor to study JS Heap size and Dom Nodes.

  1. Click on display => the list of 4000 items is displayed
  2. Click on "hide" => the list is unmounted
    When you look at performance monitor, you can see that around 8000 nodes are still in memory (and JS Heap is higher than before mounting components as well).
    If you redo 1) and 2) multiple times, you will see nodes going to 16000 then going back to 8000, ... etc.

Thus, memory is freed after the first unmount operation, but the first one is not. The weird thing is that if you do 1), 2), 2), 2), then it is freed.

CASE 2
Use the same code but remove the "componentDidMount" function in the class.
Do 1) and 2), then after few secondes memory is freed automatically (nodes and js heap) => expected behaviour

Behaviour expected

I was expected that the memory would be freed after unmounting a component, like in the 2nd case. That's a real issue when you mount.unmount big list multiple times, then js heap is going very high.

@gaearon
Copy link
Collaborator

gaearon commented Jan 18, 2019

React currently retains the previous version of an unmounted child tree until the next update. If you flip back and forth between showing and hiding the same tree, that doesn't give it a chance to be cleaned up. See #12420 (comment).

@dorianrod
Copy link
Author

dorianrod commented Jan 20, 2019

Thank you for your answer. If i understand well, it's an optimisation in case of showing/hiding elements.

What happens if the child is never flipped back? For instance, you can have two tabs A and B.
First you display A, then you display B and go back to A. When you display B, you display a heavy tree, and when you hide B, you replace the heavy content of tab B with a light placeholder.
So if i follow you the heavy tree B is still in memory. So, if you go back to B, the heavy tree can be displayed quicker because it's still in memory. But in this case, B is a consumming tab, that means that you will keep in memory the B tree whereas you will never need it. I am right?

Thus if I want to force React to release the heavy tree B in memory, I just need to update multiple times the tab with light content for instance? How do you manage it at facebook?

EDIT:
I did few tests, and adding this function to a React class allow me to get rid of the tree in memory. I don't know why but it needs two updates, one update is not enough. It doesn't seem really clean though...

forceUpdateAndClean(callback) {
	if(this.__isforcingUpdate) return; 
	this.__isforcingUpdate = true;
	this.forceUpdate(function() {
		this.forceUpdate(function() {
			this.__isforcingUpdate = false;
			if(callback) callback();
		});
	});
}

componentDidUpdate() {
        this.forceUpdateAndClean();
}

@RoystonS
Copy link

We've just had a major leak related to componentDidMount. In our case, we weren't toggling from one state to another and back, but re-rendering a whole child tree repeatedly, and observing that all the React fibers from all those previous renders - and everything they referenced - were still being kept in memory. The patch in PR #15157 fixes the problem for us; I've added more detail there (#15157 (comment)).

@stale
Copy link

stale bot commented Jan 10, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution.

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Jan 10, 2020
@stale
Copy link

stale bot commented Jan 17, 2020

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!

@stale stale bot closed this as completed Jan 17, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Resolution: Stale Automatically closed due to inactivity Type: Question
Projects
None yet
Development

No branches or pull requests

3 participants