I have a component where I am decrementing a value contained in a state variable, every second, using a useState hook :
import React, { useEffect, useState } from "react";
import { FC } from "react";
interface ITimerProps {
remainingSeconds: number;
}
const Timer: FC<ITimerProps> = ({ remainingSeconds = 10 }) => {
const [remainingTime, setRemainingTime] = useState(remainingSeconds);
useEffect(() => {
const timer = setInterval(() => {
decrementTime();
}, 1000);
return () => {
clearInterval(timer);
};
}, [remainingSeconds]);
function decrementTime (): void {
if (remainingTime > 0) {
setRemainingTime((currentTime) => currentTime - 1);
}
console.log("function gets called, state is : " remainingTime);
};
return <div>{remainingTime}</div>;
};
export default Timer;
remainingTime
,which gets rendered, is indeed decremented every second, and in the developer tools, I can see that the state is updating.
But the console.log()
inside the decrementTime
function always display a value of 10.
The if
statement within decrementTime
function also always checks true
, as it seems that when being read within this function, the state is always 10.
Is there something that I'm missing here ? Thanks
CodePudding user response:
I changed the decrementTime
function declaration from an arrow function to function decrementTime(){}
for it to get hoisted and added it in the useEffect
dependency array, now I get the correct behavior :
import React, { useEffect, useState } from "react";
import { FC } from "react";
interface ITimerProps {
remainingSeconds: number;
}
const Timer: FC<ITimerProps> = ({ remainingSeconds = 10 }) => {
const [remainingTime, setRemainingTime] = useState(remainingSeconds);
useEffect(() => {
const timer = setInterval(() => {
decrementTime();
}, 1000);
return () => {
clearInterval(timer);
};
}, [remainingSeconds,decrementTime]);
function decrementTime(): void {
if (remainingTime > 0) {
setRemainingTime((currentTime) => currentTime - 1);
}
console.log("function gets called, state is : " remainingTime);
}
return <div>{remainingTime}</div>;
};
export default Timer;
CodePudding user response:
Thats because the function itself that you are calling in the setInterval
is not updating. You have to append that function to the dependency array of the useEffect
hook. in order to avoid the eslint warning wrap the decrementTime
function with the useCallback
hook (doesn't make a difference because the function will change anyway on every render in that case).
import React, { useCallback, useEffect, useState } from "react";
import { FC } from "react";
interface ITimerProps {
remainingSeconds: number;
}
const Timer: FC<ITimerProps> = ({ remainingSeconds = 10 }) => {
const [remainingTime, setRemainingTime] = useState(remainingSeconds);
const decrementTime = useCallback(() => {
if (remainingTime > 0) {
setRemainingTime((currentTime) => currentTime - 1);
}
console.log("function gets called, state is : " remainingTime);
}, [remainingTime]);
useEffect(() => {
const timer = setInterval(() => {
decrementTime();
}, 1000);
return () => clearInterval(timer);
}, [remainingSeconds, decrementTime]);
return <div>{remainingTime}</div>;
};
export default Timer;