The problem with the above code is that every second the useEffect
gets triggered
Your useEffect hook is trying to do too much. You can split the code/logic up into as many logical effect as is necessary for your use case. In any case you will have at least 1 useEffect hook being called each time the seconds state updates so the code can correctly check when it reaches 0.
is there any way to access the value out of setSeconds calculation
to be used inside setInterval?
No, not really. In doing so you will break a few design patterns.
- The
setInterval callback creates a Javascript closure over lexical values in scope when it is created, so the callback can't see the updated seconds state value when it is called.
- You could check the current state value in the
setSeconds callback, but since React state updater functions are to be considered pure functions, you can't call functionX or cancel the interval timer from that scope.
Trivially you have at least a couple of options:
Use Two useEffect Hooks to Control Interval Timer
Use one useEffect hook with empty dependency array to initiate the interval and clean up any running intervals in the event of component unmounting, and another useEffect hook to handle the side-effect of checking when the timer expires and invoking functionX. Use a React ref to hold a reference to the timer id.
const [seconds, setSeconds] = useState<number>(START_VALUE);
const timerRef = useRef<number | null>(null);
useEffect(() => {
timerRef.current = setInterval(() => {
setSeconds((seconds) => seconds - 1);
}, 1000);
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
useEffect(() => {
if (timerRef.current && seconds <= 0) {
clearInterval(timerRef.current);
timerRef.current = null;
functionX();
}
}, [seconds]);
Use One useEffect Hook to Control Timeout Timer
Use a single useEffect hook and a setTimeout timer instead of an interval, so the effect running triggers the next timer iteration. This avoids creating extraneous unnecessary intervals your original code was doing.
const [seconds, setSeconds] = useState<number>(START_VALUE);
const functionX = () => console.log("timer expired");
useEffect(() => {
const intervalId = setTimeout(() => {
setSeconds((seconds) => seconds - 1);
}, 1000);
if (seconds <= 0) {
clearTimeout(intervalId);
functionX();
}
return () => {
clearTimeout(intervalId);
}
}, [seconds]);