I created a function, when user click start button that function and timer will start. However, when time reach 60 I want to call stop function but can't figure out how to that. can someone tell me how to do that please.
const [time,setTime] = useState(0)
const timeout = useRef()
const onStart = () => {
timeout.current = setInterval(() => {
if (time != 60) {
setTime(prevState => prevState + 1);
if (time == 60) {
onStop()
}
}
}, 1000);
}
const onStop = () => {
clearInterval(timeout.current);
}
If you don't have to use time in your JSX, then consider converting it to ref. There are certainly closure issues which will take place where the value of time state that you expect to be isn't what it will be. Your logic problem of correctly using if statements is also covered here.
const time = useRef(0)
const timeout = useRef()
const onStart = () => {
timeout.current = setInterval(() => {
if (time.current != 60) {
time.current+=1;
}
if (time.current == 60) {
onStop()
}
}, 1000);
}
const onStop = () => {
clearInterval(timeout.current);
}
And in case you need a state to be used in JSX, just make one timer as ref and time as state like so :-
const timer = useRef(0)
const [time,setTime] = useState(0);
const timeout = useRef()
const onStart = () => {
timeout.current = setInterval(() => {
if (timer.current != 60) {
setTime(prevState => prevState + 1);
timer.current+=1;
}
if (timer.current == 60) {
onStop()
}
}, 1000);
}
const onStop = () => {
clearInterval(timeout.current);
}
See this codesandbox example doing what you want :-
Here is another React way which is much less code and confusion :-
const [time, setTime] = useState(0);
const timeout = useRef();
useEffect(() => {
onStart();
}, []);
useEffect(() => {
if (time === 60) {
onStop();
}
}, [time]);
const onStart = () => {
timeout.current = setInterval(() => {
setTime((prevState) => prevState + 1);
}, 1000);
};
const onStop = () => {
clearInterval(timeout.current);
};
You are first checking if time != 60 this means when its 60 it will not enter in the statement and will not reach the second statement. I think you have to do it like this:
timeout.current = setInterval(() => {
if (time == 60) {
onStop()
}
setTime(prevState => prevState + 1);
}, 1000);
From the looks of it. Your code will never stop at 60. As the if statement if (time != 60) only runs the code if the time IS NOT 60. Then within that if statement, you've got another if statement going if time is 60 which it CAN'T be within that if statement as that code is only executed when time does not equal 60
Change
const [time,setTime] = useState(0)
const timeout = useRef()
const onStart = () => {
timeout.current = setInterval(() => {
if (time != 60) {
setTime(prevState => prevState + 1);
if (time == 60) {
onStop()
}
}
}, 1000);
}
const onStop = () => {
clearInterval(timeout.current);
}
to
const [time,setTime] = useState(0)
const timeout = useRef()
const onStart = () => {
timeout.current = setInterval(() => {
if (time != 60) {
setTime(prevState => prevState + 1);
}
if (time === 60) {
onStop();
}
}, 1000);
}
const onStop = () => {
clearInterval(timeout.current);
}
Related
I am building a pomodoro app with React and PWA feature.
I have been created a hook that helps with the countdown
useCountdown.js
import { useEffect, useState } from "react";
export function useCountdown(initialCount) {
if (typeof initialCount !== "number") {
return console.error("You must set an initial number in seconds");
}
const [intervalId, setIntervalId] = useState(null);
const [count, setCount] = useState(initialCount);
const [isCountdownFinished, setIsCountdownFinished] = useState(false);
// handling functions
useEffect(() => {
if (count === 0) {
setIsCountdownFinished(true);
} else {
setIsCountdownFinished(false);
}
}, [count]);
const countdown = () => {
// Stop countdown when reaches 0
setCount((last) => {
if (last <= 0) {
clearInterval(intervalId);
return last;
} else return last - 1;
});
};
const startCountDown = () => {
intervalId || setIntervalId(setInterval(countdown, 1000));
};
const stopCountdown = () => {
clearInterval(intervalId);
setIntervalId(null);
};
const resetCountdown = () => {
stopCountdown();
setCount(initialCount);
};
const SECS_PER_MINUTE = 60;
return [
{
minutes: Math.floor(count / SECS_PER_MINUTE),
seconds: count % SECS_PER_MINUTE,
count,
},
setCount,
startCountDown,
stopCountdown,
resetCountdown,
isCountdownFinished,
];
}
The PWA in desktop works fine, but the problem comes when I install the app in mobile, the countdown seems to stop, I notice that It is because the browser set the app in background.
This is the app https://pomo-san.vercel.app/
I expected that it could works fine as in desktop
How can I solve that?
Following Component in react-native:
import { useEffect, useState } from 'react'
let startValue = null // only using this to restart the counter from resetTimer() (other better approaches?)
export const NewTimer = () => {
const [seconds, setSeconds] = useState(startValue)
const formatedTime = () => {
return [pad(parseInt(seconds / 60)), pad(seconds % 60)].join(':')
}
useEffect(() => {
const timer = setInterval(() => setSeconds(++seconds), 1000) // I guess this line triggers the error
return () => {
clearInterval(timer)
}
}, [])
return formatedTime
}
const pad = (num) => {
return num.toString().length > 1 ? num : `0${num}`
}
export const resetTimer = () => {
startValue = 0
}
results in Uncaught Error: "seconds" is read-only
Can anyone point where the mistake is? Thx!
when you do ++seconds, you are attempting to mutate seconds this render, which isn't allowed. I would use the setState callback to get the current value, and just do seconds + 1, which will accomplish the same thing:
useEffect(() => {
const timer = setInterval(() => setSeconds((seconds) => seconds + 1), 1000)
return () => {
clearInterval(timer)
}
}, [])
I am making a countdown timer script in react.js.
Before the timer starts, a 3 or 5 seconds countdown is displayed, data for both countdown becomes from another component.
I am trying to stop/pause main countdown with a button.
My problem is how can I control data from a function that is executed by useEffect()?
I am trying crating a state for the btn, but the scope for the state is the useEffect
import TimerForm from './components/TimerForm';
const CountDown = (props) => {
const [timeLeft, setTimeLeft] = useState({
minutes: 0,
seconds: 0
});
const [counter, setCounter] = useState();
const [startTimer, setStartTimer] = useState(false);
const [result, setResult] = useState();
let Interval;
useEffect(() => {
let count = null;
if(startTimer){
if(counter === 0){
relog(result);
clearInterval(count);
return
}
count = setInterval(() => {
setCounter((prevcounter) => prevcounter - 1);
}, 1000);
return () => clearInterval(count);
} else {
clearInterval(count);
}
}, [startTimer, counter]);
const relog = useCallback((ForTime) => {
console.log(testing);
Interval = setInterval(() => {
setTimeLeft({
seconds: ForTime % 60,
minutes: Math.floor(ForTime / 60)% 60
});
if(ForTime === 0){
clearInterval(Interval);
return;
}
ForTime--;
},1000);
setStartTimer(false);
},[]);
const timerSettings = (data) => {
setCounter(data.counter);
setResult(data.result);
setStartTimer(true);
}
return (
<div>
<section>
<TimerForm onTimerSettings={timerSettings} />
<span>{counter}</span>
<div className="TimerClock">
<span>{timeLeft.minutes}</span><span>:</span><span>{timeLeft.seconds}</span>
</div>
<div>
<button type="button" className="btn btn-stop">Pause Button</button>
</div>
</section>
</div>
)
};
export default CountDown;```
create a useInterval hook used by useEffect() and useRef().
/**
*
* #param {Function} callback
* #param {number|null} delay, stopped when delay is null
*/
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
});
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
use useInterval:
function App() {
const [delay, setDelay] = useState(null);
const [counter, setCounter] = useState(0);
useInterval(() => {
setCounter((counter) => counter - 1);
}, delay);
const handleChangeDelay = () => {
// stopped when delay is null
setDelay(delay === null ? 1000 : null);
};
return (
<div className="App">
<p>
<button onClick={handleChangeDelay}>trigger interval countdown</button>
</p>
<p>{counter}</p>
</div>
);
}
You can store the interval timer by using useRef
const countRef = useRef(null);
useEffect(() => {
if (startTimer) {
if (counter === 0) {
relog(result);
clearInterval(countRef.current);
return;
}
countRef.current = setInterval(() => {
setCounter((prevcounter) => prevcounter - 1);
}, 1000);
return () => clearInterval(countRef.current);
} else {
clearInterval(countRef.current);
}
}, [startTimer, counter]);
And you can get the timer from the useEffect scope
return (
...
<button type="button" className="btn btn-stop" onClick={()=>{clearInterval(countRef.current)}}>Pause Button</button>
...
)
I'm having an issue with the auto-increment counter, which updates every 2 secs. It updates the value but in a glitchy way Please check the code and share your views regarding the problem.
const [counter, setCounter] = useState(1200)
function handleCounter() {
setCounter(counter + 1)
}
useEffect(() => {
if (counter => 1200 && counter < 1364) {
setInterval(handleCounter, 2000);
}else {
clearInterval(setInterval(handleCounter, 2000))
}
clearInterval(setInterval(handleCounter, 2000))
}, [counter])
try this
useEffect(() => {
const timeInterval = setInterval(() => {
counter < 1364 && setCounter((prevCount) => prevCount + 1);
}, 2000);
return () => {
clearInterval(timeInterval);
};
}, [counter]);
The code below will execute the handleCounter function every 2 seconds. Is it what you are trying to do?
useEffect(() => {
const intervalID = setInterval(() => handleCounter(), 2000);
return () => {
clearInterval(intervalID);
}
},[]);
How to set interval in react js first time 0 sec after that 30 sec?
//this is my state
const [state, setState] = useState();
useEffect(() => {
const interval = setInterval(() => {
APiResponse();
}, 30000);
return () => clearInterval(interval);
}, []);
const APiResponse = () => {
axios.get(`xxx`).then((res) => {
setState(res);
});
};
Set a timer variable in a setTimeOut method above your method to zero that once the component is rendered the useEffect life cycle will be called and after doing your call set it to whatever you need;
let interval;
setTimeout(()=>{
APiResponse(); // first time zero
interval = setInterval(() => {
APiResponse(); // after 30 second
} , 30000);
}, 0)
You can use a setTimeout for the first time. Then with a flag state variable that is updated after the first render, you can start your interval.
const [state, setState] = useState();
const [flag, setFlag] = useState(false);
useEffect(() => {
const interval = setTimeout(() => {
APiResponse();
}, 0);
`enter code here`;
});
useEffect(() => { if(flag === true){
const interval = setInterval(() => {
APiResponse();
}, 30000);
return () => clearInterval(interval);
}
},[flag]);
const APiResponse = () => {
setFlag(true);
axios.get(`xxx`).then((res) => {
setState(res);
});
};
You can use the code below, the idea is to invoke the setInterval callback function immediately for once and 30 sec intervals after that. Note that its an IIFE. so it wont push to task queue, instead it immediately triggers (no delay).
const [state, setState] = useState();
useEffect(() => {
const interval = setInterval(
(function callApi() {
APiResponse();
return callApi;
})(),
30000
);
return () => clearInterval(interval);
}, []);
const APiResponse = () => {
axios.get(`xxx`).then((res) => {
setState(res);
});
};
I have named the function callApi.The return callApi; line returns the same function, we can call it immediately as well as use this as a callback function to setInterval.