sharkeater123
sharkeater123

Reputation: 77

(React rendering) SetInterval just keeps going faster and unpredictable

I'm trying to implement a simple timer that just counts up continuously every second. I'm using react to render the result. The code is only a few lines and makes sense from what I've been reading. It adds and renders properly for the first 6 seconds; however, it just starts displaying random numbers after the 7th or 8th, OR the timer becomes erroneous and updates every random second. My code is as follows, am I doing something wrong? Thanks!

import React, {useState} from 'react';

function App() {

  const [count, setCount] = useState(0); 

  setInterval(()=>{setCount(count + 1)}, 1000)

  return (
    <div className="welcome">
      {count}
    </div>
  );
}

export default App;

Upvotes: 5

Views: 2402

Answers (3)

Rafael Tavares
Rafael Tavares

Reputation: 6461

Your code has some issues, as do the other answers. They can be summarized in three points:

  1. If you want to use Interval or Timeout (or any other event listener), you need to do this inside a useEffect, so you won't add a new listener in each render.

  2. If you use an useEffect for it, you'll need to clean up the Interval/Timeout (or any other listener) to avoid memory leaks.

  3. We can avoid using the count variable inside the dependencies array by using setCount(oldCount => oldCount + 1). Citing React documentation:

If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.

If you ignore step 3 by using count as a hook-dependency, you will create and clean up an Interval in each render - this is unnecessary.
If you decide to just use setCount(count + 1) without adding it to the dependency array, you're using hooks wrong, count will always have the same value (the initial one).

I added a button to force a re-render in the example below to show that the count keeps going as expected.

function App() {
  const [count, setCount] = React.useState(0);
  const [forced, setForced] = React.useState(0);

  React.useEffect(() => {
    // Store the interval id in a const, so you can cleanup later
    const intervalId = setInterval(() => {
      setCount(oldCount => oldCount + 1);
    }, 1000);
    
    return () => {
      // Since useEffect dependency array is empty, this will be called only on unmount
      clearInterval(intervalId);
    };
  }, []);
  
  function forceRerender() {
    setForced(oldForced => oldForced + 1);
  }

  return (
    <div>
      <p>{count} seconds</p>
      <p>Forced re-renders: {forced}</p>
      <button onClick={forceRerender}>Force a re-render</button>
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
<div id="app"></div>
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>

Upvotes: 3

silviubogan
silviubogan

Reputation: 3461

The setInterval call should be put inside a useEffect hook and cleanup should be done too:

import React, { useState, useEffect, useRef } from "react";

function App() {
  const [count, setCount] = useState(0);
  const ref = useRef();

  useEffect(() => {
    ref.current = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => {
      clearInterval(ref.current);
    };
  }, [count]);

  return <div className="welcome">{count}</div>;
}

export default App;

Upvotes: 1

Omar Khaled
Omar Khaled

Reputation: 499

that because it start a new one every time the component rerender use this instead

import React, {useState} from 'react';

function App() {

  const [count, setCount] = useState(0); 
  const timer = useRef(null);


useEffect(() => {
  timer.current = setInterval(()=>{setCount(count + 1)}, 1000);
  return () => {
    if(timer.current !== null) clearInterval(timer.current);
  };
}, []);

  return (
    <div className="welcome">
      {count}
    </div>
  );
}

export default App;


Upvotes: 6

Related Questions