Reputation: 91
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
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
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