I'm creating a simple tomato app and got stuck on the timer update logic.
I'm using a useEffect
to update a timer, but for some reason after the one second, the timer stop decreasing. It seems it's not getting updated since the console log keep printing the initial values .
here's a testable stackbliz
here's the full component:
import './Tomato.css';
import { useState, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faPlay, faPause, faArrowsRotate, faArrowDown, faArrowUp } from '@fortawesome/free-solid-svg-icons'
function Tomato() {
const [settings, setSettings] = useState({session: 25, break: 5});
const [timer, setTimer] = useState({minutes: '25', seconds: '00', active: false, isBreak: false});
useEffect(() => {
let timerInterval;
if(timer.active) {
timerInterval = setInterval(() => {
let seconds = parseInt(timer.seconds);
let minutes = parseInt(timer.minutes);
console.log(seconds, ':',minutes)
if(seconds === 0 && minutes === 0) {
// setTimer( {
// minutes: settings.isBreak ? settings.session : settings.break,
// seconds: '00',
// active: true,
// isBreak: !settings.isBreak
// })
// // TODO BIP
} else if(seconds === 0) {
const newTimer = {
...timer,
minutes: timer.minutes-1,
seconds: '59',
}
setTimer( newTimer)
} else {
console.log('in')
seconds = seconds - 1;
// this is not working as expected
setTimer( {
...timer,
seconds: seconds < 10 ? '0' seconds : seconds ''
})
}
}, 1000);
} else {
clearInterval(timerInterval);
}
return function cleanup() {
clearInterval(timerInterval);
};
}, [timer.active]);
const toggleTimer = (value) => {
setTimer({ ...timer, active: value})
}
const refreshTimer = () => {
setTimer({
minutes: settings.session,
seconds: '00',
active: false,
isBreak: false
})
}
const editSettings = (type, val) => {
if(timer.active) return
const newSettings = { ...settings }
newSettings[type] =val;
if(newSettings[type] > 60) {
newSettings[type] = 60
}
if(newSettings[type] < 1) {
newSettings[type] = 1
}
setSettings(newSettings)
}
return (
<div className="Tomato">
<h2 className="title">Session</h2>
<div className="session-container">
<div className='session-value'>{timer.minutes}:{timer.seconds}</div>
<div className='controls-container'>
<span className='icon-container'>
<FontAwesomeIcon onClick={()=>{ toggleTimer(true)}} icon={faPlay} />
</span>
<span className='icon-container'>
<FontAwesomeIcon onClick={()=>{ toggleTimer(false)}} icon={faPause} />
</span>
<span className='icon-container'>
<FontAwesomeIcon onClick={()=>{ refreshTimer()}} icon={faArrowsRotate} />
</span>
</div>
</div>
<h2 className="title">Session Length</h2>
<div className="setting-container">
<span className='icon-container' onClick={()=> editSettings('session', -1)}>
<FontAwesomeIcon icon={faArrowDown} />
</span>
<div className="setting-value">{settings.session}</div>
<span className='icon-container' onClick={()=> editSettings('session', 1)}>
<FontAwesomeIcon icon={faArrowUp} />
</span>
</div>
<h2 className="title">Break Length</h2>
<div className="setting-container">
<span className='icon-container' onClick={()=> editSettings('break', -1)}>
<FontAwesomeIcon icon={faArrowDown} />
</span>
<div className="setting-value">{settings.break}</div>
<span className='icon-container' onClick={()=> editSettings('break', 1)}>
<FontAwesomeIcon icon={faArrowUp} />
</span>
</div>
</div>
);
}
export default Tomato;
CodePudding user response:
That's the typical stale state issue, timer
inside the interval is stale since you are not placing it into the effect deps. Just change timer.active
to timer
in the deps array.
...
return function cleanup() {
clearInterval(timerInterval);
};
}, [timer]);
If you want to know more about this specific issue: https://overreacted.io/making-setinterval-declarative-with-react-hooks/