user3033194 user3033194 - 3 months ago 87
React JSX Question

setInterval timer not working properly in render() of ReactJS

I have a countdown timer in a React component, which counts for 10 seconds. If within those 10 seconds the component receives backend data, the timer is stopped. If not, it will count down to 0, at which point the page will refreshed, and count down again, and so on, until the data is received. My code is given below:

constructor() {
...
this.counter = 10;
...
}

render() {
const interval = setInterval(() => {
const result = this.state.data;
if (result) {
this.setState({
resultsReceived: true
});
clearInterval(interval);
} else {
this.setState({
resultsReceived: false
});

this.counter = this.counter - 1;
if (this.counter === 0) {
window.location.reload();
}
}
}, 1000);


The problem is, the timer does not appear to decrement every second. Instead, the behavior is erratic - from 10 to maybe 7 it is very fast, then it may hang or even go on after 0, i.e. negative. The page is reloading all right. Is this code wrong, or is it a problem related to React's state? Any help would be great!

UPDATE:

I changed the code to this:

constructor() {
this.state = {
...
interval: '',
counter: 10
};
}

componentWillMount() {
$.ajax({
url: this.props.api,
datatype: 'json',
cache: false,
success: (data) => {
this.setState({ data });
}
});
const interval = setInterval(() => {
const results = this.state.data;
if (results) {
this.setState({
resultsReceived: true
});
clearInterval(this.state.interval);
} else {
this.setState({
resultsReceived: false
});
let counter;
counter = this.state.counter - 1;
this.setState({ counter });
if (this.state.counter === 0) {
window.location.reload();
}
}
}, 1000);
this.setState({ interval });
}

componentWillUnmount() {
clearInterval(this.state.interval);
}


It is better than before in that the timer works properly the first time - it goes from 10 to 0 every 1 second. However, when even after 10 seconds the data has not loaded and the timer starts from 10 again, it reaches 9 and then stops, until the data finally loads. The page also becomes very slow.

Answer

I'm not sure why this is happening but I'm sure your code is somewhat very strange.

  1. First of all, keeping your interval to state is very strange. Instead, keep it in your component instance so that you can clear it easily

  2. Do not set data and check it with interval. You can release interval when your api request is done and set data to state.

  3. Always clear interval before component is dismissed.

This is what I can suggest.

constructor() {
  ...
  this.interval = null
  this.state = { counter: 10 }
  ...
}

componentWillMount() {
  $.ajax({
    url: this.props.api,
    datatype: 'json',
    cache: false,
    success: (data) => {
      this.setState({ data })
      clearInterval(this.interval);
    },
  });

  this.interval = setInterval(() => {
    if (counter <= 0) {
      clearInterval(this.interval);
      location.reload();
      return;
    }

    this.setState({
      counter: this.state.counter - 1,
    });
  }, 1000);
}

componentWillUnmount() {
  clearInterval(this.interval)
}

actually if you don't use interval and setTimeout it would be much better if your state.counter exists only to check how many second has passed.

constructor() {
  ...
  this.timeout = null
  ...
}

componentWillMount() {
  $.ajax({
    url: this.props.api,
    datatype: 'json',
    cache: false,
    success: (data) => {
      this.setState({ data })
      clearTimeout(this.timeout);
    }
  });

  this.timeout = setTimeout(() => {
    clearTimeout(this.timeout);
    location.reload();
  }, 1000 * 10);
}

componentWillUnmount() {
  clearTimeout(this.timeout)
}

setInterval is expensive. Don't use it for this kind of work.