Description
Do you want to request a feature or report a bug?
Report a bug.
What is the current behavior?
ReactDOM keeps references to previous states/props/children when component gets updated. All in all consuming three times as much memory as it really needed.
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:
Link to the example below (using production versions of react
and react-dom
):
https://codesandbox.io/s/epic-bartik-pvgqx.
Consider following example:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
let dataInstanceCount = 0;
class MyBigData {
constructor() {
const id = `my-big-data:${dataInstanceCount++}`;
this.getMyDataId = () => id;
this.data = new Array(100000).fill('');
}
}
let componentInstanceCount = 0;
class MyItem extends React.Component {
constructor(props) {
super(props);
this._myItemId = `my-item:${componentInstanceCount++}`;
this.state = {list: []};
}
render() {
return this.props.item.getMyDataId();
}
}
class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {list: []};
}
componentDidMount() {
this.updateList(() => {
this.updateList(() => {
this.updateList();
});
});
}
updateList(callback) {
this.setState({
list: [new MyBigData()]
}, callback);
}
render() {
return this.state.list.map((item) => (
<MyItem key={item.getMyDataId()} item={item} />
));
}
}
const rootElement = document.getElementById('root');
ReactDOM.render(
<MyApp />,
rootElement
);
setTimeout(() => {
console.log(
rootElement._reactRootContainer._internalRoot.current.alternate.firstEffect.memoizedProps.item.getMyDataId(),
rootElement._reactRootContainer._internalRoot.current.alternate.firstEffect.stateNode._myItemId
);
// > my-big-data:0, my-item:0
console.log(
rootElement._reactRootContainer._internalRoot.current.firstEffect.memoizedProps.item.getMyDataId(),
rootElement._reactRootContainer._internalRoot.current.firstEffect.stateNode._myItemId
);
// > my-big-data:1, my-item:1
console.log(
rootElement._reactRootContainer._internalRoot.current.lastEffect.memoizedProps.item.getMyDataId(),
rootElement._reactRootContainer._internalRoot.current.lastEffect.stateNode._myItemId
);
// > my-big-data:2, my-item:2
}, 1000);
I expect only one MyBigObject
and one MyItem
component to be in the memory. But instead I can see three of each in memory heap snapshot.
UPDATE
As shown in the updated example the references to these objects and components can be accessed in the sub-properties of the root DOM element.
What is the expected behavior?
There's no justifiable reason to keep in memory unmounted components and previous states/props of component after it was updated.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
React 16.9.0, ReactDOM 16.9.0 (Production versions)
Mac/Win
Activity
prabhjyot28 commentedon Jul 16, 2019
Memory leak occurs when, you change the state of an unmounted component, so to remove this kind of warning, you better check is your component is mounted or not before updating its state.
To do this =>
Create a property _isMounted and set it to false inside your components (that have states/Smart Component) :
_isMounted = false;
Now set it to true, when your component gets mounted & set it to false when it gets unmounted:
componentWillMount()
{
this._isMounted = true;
}
componentWillUnmount()
{
this._isMounted = false;
}
Now every time before updating the state, check whether component is mounted or not, for your case:
setInterval(() => {
if(this._isMounted)
{
this.setState({
list: [new MyBigObject()]
});
}
}, 500);
dmitrysteblyuk commentedon Jul 17, 2019
Memory leak occurs when, you change the state of an unmounted component, so to remove this kind of warning, you better check is your component is mounted or not before updating its state.
As you can see in the example I'm not unmounting
MyApp
component at any point.But there's still a memory leak (multiple instances of
MyBigObject
are in the memory, despite only one of them is in the state).dmitrysteblyuk commentedon Jul 24, 2019
Garbage collection is not deterministic.
Probably not, but chrome memory heap snapshot certainly is.
In the updated example I console logged the sub-properties of rood div element to show that the redundant data and components still have references.
Which means they cannot be garbage collected.
gaearon commentedon Aug 15, 2019
Can you verify whether 16.9 solves this?
dmitrysteblyuk commentedon Aug 22, 2019
I updated the example link. The external resources now point to react/react-dom 16.9.0 versions.
The issue is still present.
amrsekilly commentedon Sep 2, 2019
@gaearon I upgraded both
React
andReactDOM
to16.9.0
, but it doesn't seem to fix the issue.jacky-nirvana commentedon Sep 9, 2019
I am facing the same problem.
I have a list(for example: list = [10, 20, 30]), use list.map return JSX.Element[],
after the list changed, for example list => [20, 30, 40], the Elements' count are still 3, but fibernode' count is increased, the deleted Element(element of 10)' fibernodes are referenced already.
React & React-Dom: 16.9.0
OS: macOS v10.14.6
Browser: Chrome 76.0
jzworkman commentedon Oct 8, 2019
Is there any update on this issue? We are seeing this as a significant issue when using Redux and container components. When our container componet(that is connected to redux store) passes props to the child components, and then the redux store updates. The child component props are being stranded in the dom with old reference(seen in the heap after forcing a garbage collection cycle). This is causing huge amounts of memory bloat when on a page that is connected to signalR for real time collaboration between users(as each redux update creates hundreds of stranded object references in the child components).
I have verified that this is the cause by instead having all of the previously "dumb" pure child components be converted to Connected components and pull their props from redux instead of having the container component pattern control all of the store connections. this then correctly all references the single redux store object and garbage collection works as expected without stranded references.
BrandonGillis commentedon Nov 14, 2019
Any news on this issue ?
shermanrxie commentedon Nov 18, 2019
Any update on this?
joaompneves commentedon Nov 21, 2019
I have an app with lots of components that get unmounted all the time and this is a big issue.
I tried to reproduce this problem in a very simple example (https://jsfiddle.net/vne79fzs/), and I'm concerned how easy is to leak memory without developer being aware.
As far as I know the problem occurs at least since 16.0 and is still there (16.12).
Is there any viable workaround? I tried to cleanup the state field, but the memory is hold by other fields.
sbrichardson commentedon Nov 23, 2019
I'm seeing this issue also, I noticed because I was working with large CSV/DB files with a Web Worker.
I tried a few different tests and noticed my case was linked to a Detached system/Context from a resize observer that was using useEffect/useState. It was keeping a reference to the state of a nearby component somehow.
It was an Animation test component that I could test for 60fps while parsing a 75MB file using a Web Worker.
I came across this when doing the following:
The previous state (large results) will still be retained after unmount, and can be seen in the heap snapshot from Chrome Dev Tools.
If I hard refresh the browser, the memory will clear as expected
stale commentedon Feb 21, 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.
8 remaining items