не работает таймер в React. Использую useEffect и useState
Прошу помочь. Код вроде простой, но я что-то явно не понимаю :) В моём понимании: Компонент монтируется, и запускается timer. Каждую секунду он уменьшает seconds на 1, а значение seconds в свою очередь отображается на странице. В реальности, отображается число 59 и всё. Дальше не идёт.
const [seconds, setSeconds] = useState(60)
useEffect(() => {
const timer = setInterval(() => {
setSeconds(seconds - 1)
if (seconds < 0) {
clearInterval(timer)
props.closeTimer()
}
}, 1000)
}, [])
return (
<div className='LoaderTimer LoaderTimer_active'>
<p className="LoaderTimer__timer">{seconds}</p>
<div className='LoaderTimer__holder'>
<div className="LoaderTimer__preloader">
</div>
</div>
</div>
)
}```
Ответы (1 шт):
В реальности, отображается число 59 и всё
Причина почему так происходит это у каллбэк функции который вы передали к setInterval есть доступ к переменной seconds только в первом render-е.У него нет доступа к переменной seconds во втором render-е потому что useEffect не вызывается второй раз.
Переменная seconds имеет значение 60 всегда внутри setInterval
В таком случае для решения этой проблемы у вас есть два пути.
Первый: Передать массиву зависимостей переменную seconds чтобы useEffect вызывался после каждого обновления seconds
const [seconds, setSeconds] = useState(60)
useEffect(() => {
const timer = setInterval(() => {
if (seconds > 0) {
setSeconds(seconds - 1);
}
}, 1000)
return () => clearInterval(timer)
}, [seconds]);
return (
<div className="App">
<p className="LoaderTimer__timer">{seconds}</p>
</div>
);
Второй: Использовать функциональное обновления.Таким образом каллбек всегда будет получать актуальный стейт БЕЗ использования массива зависимостей useEffect.
const [seconds, setSeconds] = useState(60);
useEffect(() => {
const timer = setInterval(() => {
if (seconds > 0) {
setSeconds((sec) => sec - 1);
}
}, 1000);
return () => clearInterval(timer);
}, []);
return (
<div className="App">
<p className="LoaderTimer__timer">{seconds}</p>
</div>
);
Минус второго варианта в том что дойдя до значения нуля наш таймер не остановится. Почему так происходит? Потому что useEffect не обновляется соответственно это линия кода if (seconds > 0) { не работает.
Для того чтобы решить эту проблему нужно использовать useRef
const [seconds, setSeconds] = useState(60);
const intervalRef = useRef();
useEffect(() => {
intervalRef.current = setInterval(() => {
setSeconds((sec) => sec - 1);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
const cancelInterval = () => {
if (seconds < 1) {
clearInterval(intervalRef.current);
}
};
cancelInterval();
return (
<div className="App">
<p className="LoaderTimer__timer">{seconds}</p>
</div>
);
Таким образом мы в каждом render-е проверяем если значение меньше один то останавливаем таймер иначе он продолжает работать.
Также о работе setInterval в React можно прочитать тут