yoonjae
yoonjae

Reputation: 91

setInterval() called twice at an interval [React js]

enter image description here

Please look at the time printed on the console.

setInterval() looks like being called twice in on interval.

I attached my code below. I'd really appreciate it if you could give me a clue to the solution.

import React, {useState} from 'react';
import AppRouter from './Router';
import {authService} from '../fbase';

function App() {
  console.log('App()');
  const [isLoggedIn, setIsLoggedIn] = useState(authService.currentUser);
  console.log(authService.currentUser);
  setInterval(() => {
    console.log(Date().toLocaleString());
    console.log('In App.js > App()');
    console.log(authService.currentUser);
  }, 5000);
  return <AppRouter isLoggedIn={isLoggedIn}></AppRouter>;
}

export default App;

Upvotes: 4

Views: 8915

Answers (2)

fum4
fum4

Reputation: 297

Every time a state/prop changes your component will re-render. This means that everything that is not wrapped in a hook of some kind will also re-run. I'm not sure why this happens to you, maybe authService.currentUser returns two different values, an initial (probably empty) one when mounting the component and the correct one after it does some calculations (maybe you request the user from the back-end and it takes a while to get back the response -- some more code would be helpful here).

When the actual (correct) value will be returned from the authService to your function component it will re-render thus running setInterval again.

In order to make sure this never happens it's best to wrap our functionalities in a hook. If you want to run the setInterval just ONCE (when mounting the component) we can wrap it in an useEffect with an empty dependency array:

useEffect(() => {
  setInterval(() => {
    ...
  }, 5000)
), []}

This means the hook will only run when your component first mounts.

If you need to run it based on some prop change (like when isLoggedIn changes from false to true or viceversa) you can add that to the dependency array and your interval will run every time isLoggedIn state changes:

useEffect(() => {
  setInterval(() => {
    ...
  }, 5000)
}, [ isLoggedIn ])

If you only need to run that when isLoggedIn changes from false to true you can also add an if condition in your useEffect like this:

useEffect(() => {
  if (isLoggedIn) {
    setInterval(() => {
      ...
    }, 5000)
  }
}, [ isLoggedIn ])

More than that, as Jose Antonio Castro Castro mentioned, in all of the above cases you need to use a cleanup function in order to stop the interval from running indefinitely when your component unmounts (because it has no way of knowing to stop by itself). This is achieved by returning a function from your effect:

useEffect(() => {
  const interval = setInterval(() => {
    ...
  }, 5000)

  return () => clearInterval(interval);
), []}

Don't forget that every time your component feels like re-rendering, all of your constants and functions that are called directly at the root of your component will be re-declared / re-run again.

The useEffect, useCallback, useMemo hooks help us with exactly that, but in different ways. Choose and use them wisely!

Upvotes: 5

Tonio
Tonio

Reputation: 1782

As in the comments, you need to call setInterval when you mount the component, but you need to stop the timer when it is unmounted.

const App = (props) => {
  const [count, setCount] = React.useState(0)

  React.useEffect(function appRunTimer() {
    // Creates a new timer when mount the component.
    const timer = setInterval(() => {
      console.log(Date().toLocaleString())
      console.log('In App.js > App()')
    }, 5000)
    
    // Stops the old timer when umount the component.
    return function stopTimer() {
      clearInterval(timer)
    }
  }, [])
  
  console.log('DRAW')
  return (
    <button onClick={() => {setCount(count + 1)}}>
      Click to update component's state: You click me {count} time(s)
    </button>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<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>

<div id="root"></div>

Upvotes: 3

Related Questions