I'm trying to periodically run a calculation (every 5 seconds) and update a component's state with the calculated value using a setInterval timer. What I've seen is that the updateCalculation() function does get called every 5 seconds but when monitoring the memory usage using the Chrome devtools it just keeps on growing endlessly on every call by setInterval. The memory never seems to get released.
Snapshot 1:
Snapshot 2:
What could be a possible workaround for running calculations periodically?
I'm still pretty new to React and am really not sure what I'm doing wrong.
class MyComponent extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
calculated: []
};
}
componentDidMount() {
this.calculationUpdater = setInterval(() => this.updateCalculation(), 5000);
}
componentWillUnmount() {
clearInterval(this.calculationUpdater);
}
// Memory leak here
// The function that gets called by setInterval to calculate data and update the state
updateCalculation() {
let data = [];
for (let i = 0; i < 60000; i++) {
data.push({x: i, y: i, z: i});
}
this.setState({
calculated: data
});
}
render() {
return (
<React.Fragment>
<Child calc={this.state.calculated} />
</React.Fragment>
);
}
}
I'm not doing anything special with the Child component at the moment. This is what it looks like:
class Child extends React.PureComponent {
render() {
return (
<div></div>
);
}
}
EDIT: Check following post: Does JavaScript setInterval() method cause memory leak?
You are not clearing the interval because you are are not setting or reading state correctly. So if your component keep getting mounted and unmounted, you set a new interval but do not clear the interval on unmount.
this.calculationUpdater = setInterval(() => this.updateCalculation(), 5000);
This should be
const calculationUpdater = setInterval(() => this.updateCalculation(), 5000);
console.log(calculationUpdater )
this.setState({calculationUpdater : calculationUpdater})
And you access the state as following:
console.log(this.state.calculationUpdater);
clearInterval(this.state.calculationUpdater);
The only thing I can suggest is to try switching from PureComponents to Components; I noticed my initial snapshot was laggier for PureComponents, though either way my results were not like yours.
Related
I am working on my personal Portfolio using React and I want to add a statement on the landing page that displays my local time and timezone to recruiters, as seen below:
I have implemented this using the date-fns library but I am currently facing one issue. As time goes by, the time values on the screen stay constant and do not update themselves automatically. As we speak, it is currently 16:58 but the time still shows 16:47. I have to do a manual refresh for the updated time values to show. I want to implement this in such a way that the time will update every 60 seconds and show the current time always.
I wrote this Class component to implement the feature:
export class Time extends Component {
constructor() {
super();
var today = new Date();
var time = format(today, 'HH:mm')
this.state = {
currentTime: time
}
}
render() {
return (
<div className="time">
<p>
My Local Time is { this.state.currentTime } GMT +3
</p>
</div>
);
}
}
setInterval(Time, 60000);
What could I do to make this possible?
I tried using the solution provided Luke-shang-04 but I ran into issues with declaration of the intervalId variable. I therefore resorted to using React Hooks and converted this component into a Functional component.
The code below works:
export const Time = () => {
const [time, setTime] = useState(new Date());
useEffect(
() => {
const intervalId = setInterval(() => {
setTime(new Date());
}, 60000);
return () => {
clearInterval(intervalId)
}
}
)
return(
<div>
<p>{`My local time is ${format(time, 'HH:mm')} GMT+3`} </p>
</div>
)
}
Create a componentDidMount method, and place the setInterval in there. Make sure that the setInterval updates this.state.currentTime. It's also a good idea to clear the interval afterwards, so store the interval id and then call clearInterval in componentWillUnmount.
Something like this
export class Time extends Component {
intervalId
constructor() {
super()
var today = new Date()
var time = format(today, "HH:mm")
this.state = {
currentTime: time,
}
}
componentDidMount() {
var today = new Date()
var time = format(today, "HH:mm")
intervalId = setInterval(() =>
this.setState({
currentTime: time,
}),
60000
)
}
componentWillUnmount() {
clearInterval(intervalId) // Clear interval to prevent memory leaks
}
render() {
return (
<div className="time">
<p>My Local Time is {this.state.currentTime} GMT +3</p>
</div>
)
}
}
I would suggest that you read up on the React Docs, since these are the basics of React.
The problem i have is that React does not update in the situation below.
I added a forceUpdate() when the component should update just to make extra sure.
The code is simple so there is not much to say.
It's as if React does not see that it should update or am i doing something really wrong here?
class Greetings extends React.Component{
constructor(props){
super(props)
this.switchLanguage = this.switchLanguage.bind(this)
this.state = {
languageID: 0,
}
this.arrayContainingRenderValues = [
<span>{this.props.greetingArray[this.state.languageID]}!</span>,
<span>No greetings for you!!</span>
]
}
switchLanguage(){
this.setState((previousState) => ({languageID: (previousState.languageID + 1) % this.props.greetingArray.length}))
this.forceUpdate()
}
componentDidMount(){
this.timerID = setInterval(this.switchLanguage, 500)
}
componentWillUnmount(){
clearInterval(this.timerID)
}
render(){
return this.arrayContainingRenderValues[0]
//The return below works without problem
return <span>{this.props.greetingArray[this.state.languageID]}!</span>
}
}
let content = <Greetings greetingArray={["Good morning","Bonjour","Buenos días","Guten tag","Bom dia","Buongiorno"]}/>
ReactDOM.render(content, document.getElementById('root'))
The state gets updated, you can see that simply by commenting out the first return.
A i got an answer, it is just that the value of the content in this.arrayContainingRenderValues[] was computed and then fixed when first assigned inside the constructor(), to have it recompute the array had to be reassigned in the render().
So in the end i may as well not use the array at all. But i just wanted to test how react works thanks for the help.
I'm working on a React app, and I want to manage the user inactivity.
For it, I defined a countdown which is supposed to be reset to his original value if the user is doing something in the App.
The displayed components are rendered/surrounded by a Layout component.
My problem is that the layout is updated twice after every user action, since the .setState function is used in it. Therefore, the InactivityManager Component also is updated twice and the setInterval is executed twice at the same time.
I wrote a simple InactivityManager Component, which isn't rendering anything but is rendered in the Layout.
Here is the component:
import { Component } from 'react';
import { isLogged, logout } from '...';
class InactivityManager extends Component {
constructor(props) {
super(props);
this.refreshRemainingTime = this.refreshRemainingTime.bind(this);
}
componentDidUpdate() {
if (isLogged()) {
clearInterval(this.refreshRemainingTime);
localStorage.setItem('activityCountdown', '900');
window.setInterval(this.refreshRemainingTime, 5000);
}
}
refreshRemainingTime = () => {
let activityCountdown = parseInt(localStorage.getItem('activityCountdown'), 10);
activityCountdown -= 60;
localStorage.setItem('activityCountdown', activityCountdown);
if (activityCountdown <= 0) {
logout();
localStorage.removeItem('activityCountdown');
}
};
render() {
return null;
}
}
export default InactivityManager;
Any idea of what is the best approach? Also tried to include the Component in the App.js but the Component expect only one "Route" child.
Thanks!
Can you modify the componentDidUpdate method a little bit.
componentDidUpdate() {
if (isLogged()) {
if (this.interval) clearInterval(this.interval);
localStorage.setItem('activityCountdown', '900');
this.interval = setInterval(this.refreshRemainingTime, 5000);
}
}
I finally prefered to use setTimeout instead of setInterval, there is no problem anymore with this.
Thanks!
This questions is actually React JS related. Is it OK to define internal class variables inside one of the class methods and then use it in other methods? I mean to do something like this:
class Something extends React.Component {
state = {
value: 'doesnt matter'
};
something = () => {
//a lot is happening in here and at some point I define this.thing
this.thing = 1;
}
increase = () => {
if(this.thing) {
this.thing += 1;
}
}
decrease = () => {
if(this.thing && this.thing > 0) {
this.thing -= 1;
}
}
render() {
return (
<span>this.state.value</span>
);
}
}
thing is that I don't need to put that this.thing as a state value, because I only need it internally. Please be aware that this code is just an example, real code is a bit more complicated, but the main question, is it OK to define class internal variables(this.thing) like I do in my example? Or maybe I should do this differently? What would be the best practice?
It's not a problem to use the constructor to do such a thing but based on the react theory and UI rendering this kind of usage will not re-render or follow the react pattern of trigger and re-render, it will just server as a storage for a value that has nothing to do with the react life cycle.
class Something extends React.Component {
constructor(props) {
super(props);
// Your thing
this.thing = 0;
this.state = {
value: "doesnt matter."
};
}
something = () => {
//a lot is happening in here and at some point I define this.thing
this.thing = 1;
};
increase = () => {
if (this.thing) {
this.thing += 1;
}
};
decrease = () => {
if (this.thing && this.thing > 0) {
this.thing -= 1;
}
};
render() {
this.something();
console.log(this.thing); // will equal 1.
return <span>{this.state.value}</span>;
}
}
I don't need to put that this.thing as a state value, because I only need it internally.
A React component's state should also only be used internally.
What would be the best practice?
You can use instance variables (ivars) instead of state to increase performance because you may reduce the burden on the event queue. Aesthetically, ivars often require less code. But state updates are usually preferred because they will trigger a re-render; this guarantee makes your code easier to think about, as the render is never stale. In your case, the render function is independent of this.thing, so it's okay to store it in an ivar.
Generally, it's best to initialize ivars in the constructor because it runs first, so this.thing is guaranteed to be ready for consumption by other methods:
constructor(props) {
super(props);
this.thing = 0;
}
The first time my array is rendered it is in the correct order, however, if it is changed the rendered order remains the same.
For example:
construct() {
this.state = {
test_array: [1,2,3,4]
}
let self = this;
setTimeout(function(){
self.scramble();
}, 5000);
}
scramble() {
this.state.test_array = [3,1,2,4];
this.setState(self.state);
}
render() {
this.state.test_array.forEach(function(item){
console.log(item);
});
return (
<div>
{this.state.test_array}
</div>
);
}
Results in:
On the console (the current order, correct):
3
1
2
4
Rendered as DOM (the original order, incorrect):
1
2
3
4
Any idea why this is failing to render in the correct order?
You were very close. Here's a few things I changed to fix it:
construct should be constructor
You always need to call super() as the first line of a constructor. (You don't really need to worry about this, it's an Object Oriented thing; google it if you're curious)
Use "arrow functions" instead of "keyword functions" or .bind(this) to prevent this from changing contexts
Do not modify this.state; always call this.setState if you want it to change
class OrderThing extends React.Component {
constructor() {
super()
this.state = {
test_array: [1,2,3,4]
}
setTimeout(() => {
this.scramble();
}, 5000);
}
scramble() {
this.setState({
test_array: [3,1,2,4]
});
}
render() {
this.state.test_array.forEach(function(item){
console.log(item);
});
return (
<div>
{this.state.test_array}
</div>
);
}
}
const div = document.createElement('div')
document.body.appendChild(div)
ReactDOM.render(<OrderThing />, div)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
A few suggestions here.
First of all, there is no construct() in js, but there is constructor().
Secondly, you should always call super method with props as an argument in constructor, like this:
constructor(props) {
super(props);
...
}
Finally, react developers highly recommend to modify state only using setState() method.
So you should rewrite your scramble method.
scramble() {
this.setState({test_array: [3,1,2,4]});
}
This changes should help you a little bit.