In my react component, once the component loads, I am trying to repeat a task every 100ms and after 3 repetitions pause for 1 second. Then repeat this pattern indefinitely.
I want to achieve this output:
1 // pause 100ms
2 // pause 100ms
3 // pause 100ms
// pause 1second
... repeat
I tried something like this
useEffect(() => {
let i = 0
function increment() {
if (i === 3) {
// i = 0
// restart timer?
// return?
}
i++
console.log(i)
}
const incrementTimer = setInterval(increment, 100)
setInterval(() => {
clearInterval(incrementTimer)
}, 1000)
}, [])
You can use a recursive function within useEffect hook:
useEffect(() => {
function start(i = 1) {
const reset = i > 3;
const time = reset ? 1000 : 100;
const timeout = setTimeout(() => {
task(i, time); // -> run your task
start(reset ? 1 : i + 1); recursive call to schedule next task
clearTimeout(timeout); // -> clear
}, time);
}
start();
}, []);
function task(i, time) {
console.log('task is running: ', i, time);
}
Working example: https://stackblitz.com/edit/react-jenzmw?file=src%2FApp.js
You can achieve this via setTimeout.
useEffect(() => {
let i = 0
let incrementTimer
function increment() {
if (i === 3) {
// i = 0
// restart timer?
// return?
}
i++
console.log(i)
incrementTimer = setTimeout(increment, 100)
}
increment()
setInterval(() => {
clearInterval(incrementTimer)
}, 1000)
}, [])
First you need to define a state to determine at which level the component is.
const [levelIndex, setLevelIndex] = useState(0);
Then create an array for execution time at each level (This is For the case that you are looking for, it can be changed depending on what time spans are needed or which pattern a user wants).
const intevalExecutionTime = [100,100,100,1000];
Next create a useEffect hook like this:
useEffect(() => {
const timer = setInterval(() => {
//**write the task that you want to be done.**
if(levelIndex === 3){
setLevelIndex(0);
console.log(`one second paused`);
}
else{
setLevelIndex(levelIndex+1);
console.log('task is done.');
}
clearInterval(timer);
}, intevalExecutionTime[levelIndex])
}, [levelIndex]);
This hook callback will set an interval based on the interval execution time level. Every time the setInterval callback is triggered the older interval object will be removed and it changes the levelIndex and the useEffect callback will be called once again.
Related
building a speed-type-game in react
in my app there countdown feature using setinterval function
me want to stop countdown when it reaches 0
my code here
const countdownValue = 15
const [sec , setSec ] = useState(countdownValue)
let timer
function start(){
timer = setInterval( () => {
setSec( sec => sec - 1)
},1000)
}
if(sec <= 0){
clearInterval(timer)
}
me tried clearinterval function to stop countdown but its not working
the component re-renders after the state change. So your timer becomes undefined. What you can do is: move it outside your component or use useRef from React
const countdownValue = 15
const [sec , setSec ] = useState(countdownValue)
const timer = useRef()
function start(){
timer.current = setInterval( () => {
setSec( sec => sec - 1)
},1000)
}
useEffect(() => {
if (sec <= 0) {
clearinterval(timer.current)
}
}, [sec])
I am trying to build a component that auto scrolls through some list, forwards and then backwards.
const [clicks, setClicks] = useState(numOfClicks)
const [goingUp, setGoingUp] = useState(true)
const scroll = () => {
if (goingUp) {
elementsRef.current[current + 1].current.scrollIntoView();
setCurrent(current + 1);
setClicks(clicks - 1);
} else {
elementsRef.current[current - 1].current.scrollIntoView();
setCurrent(current - 1);
setClicks(clicks - 1);
}
}
if clicks reaches 0 the list is at its end and then it flips around and goes backwards until it reaches the start and then the cycle repeats.
useEffect(() => {
if (clicks === 0) {
setGoingUp(!goingUp);
setClicks(numOfClicks);
}
}, [clicks])
up to this point, it works fine via click events. My last phase is to execute scroll automatically within the component after an interval. For this I use an additional useEffect
useEffect(() => {
const interval = setInterval(() => {
scroll()
}, TIMER);
return () => clearInterval(interval); // This represents the unmount function, in which you need to clear your interval to prevent memory leaks.
}, [])
The function runs once and then never again. Although if I add a console.log within the effect I see the interval is in fact working.
clearInterval is causing the setInterval to stop after 3 sec , it only run once , put your clearInterval inside a condition which will specify when you want this to stop
useEffect(() => {
const interval = setInterval(() => {
scroll()
}, TIMER);
return () => clearInterval(interval);
}, [clicks])
I took code based off this page and adjusted it. I want to time the amount of milliseconds the user is on the component so I want to log the counter value when the component unmounts aka the return statement of useffect/componentWillUnmount().
const [milliseconds, setMilliseconds] = useState(0);
const isActive = useState(true);
const logger = new logger(stuff);
useEffect(() => {
initializeIcons(undefined, { disableWarnings: true });
});
useEffect(() => {
return () => {
console.log("empty useffect milliseconds:", milliseconds);
logger(milliseconds);
clearInterval(milliseconds)
};
}, []);
useEffect(() => {
let interval: NodeJS.Timeout = setInterval(() => {
}, 0);
interval = setInterval(() => {
setMilliseconds(milliseconds => milliseconds + 1000);
}, 1000);
console.log("interval:", interval);
console.log("interval milliseconds:", milliseconds);
}, [ milliseconds]);
I see the millisecond printout fine in the "interval milliseconds" console statement but the "empty useffect milliseconds:" always prints out 0. What am I doing wrong?
You can remember a mount timestamp and then calculate the difference.
useEffect(() => {
const mountedAt = Date.now();
return () => {
const mountedMilliseconds = Date.now() - mountedAt;
console.log(mountedMilliseconds);
};
}, []);
Side note 1: use an empty array as deps if you want to run function on mount only. If you do not pass [] deps, your initializeIcons effect will run with each re-render. Do it like this:
useEffect(() => {
initializeIcons(undefined, { disableWarnings: true });
}, []);
Side note 2: first interval you create creates a memory leak, because it does nothing, and is never cleared.
Another problem you have is milliseconds dependency in useEffect, which registers new intervals after each milliseconds state change.
how can i call two permanent services, i want to call the first service every 5 seconds, after once results of the first service return a value different from 0, I want to call a second service with the value returned by the first service every 5 seconds also, until I receive a value, How can I do this with react setInterval doesn't work.
const {info,data} = useSelector((state) => ({
info : state.reducerData.info,
data : state.reducerData.data,
})
useEffect(() =>{
var refreshIntervalId = setInterval(() => {
dispatch(getData(id));
},(5000))
if(data?.sum != 0){
clearInterval(refreshIntervalId)
}
},[id])
useEffect(() =>{
var interval = setInterval(() => {
if(data?.sum !=0){
dispatch(getInfoData())
}
},(5000))
if( info == sum){
clearInterval(interval)
}
},[data])
I've created a simple timer script initialized with 10 seconds for testing. The problem I'm having is that the pause timer button isn't working as expected.
import React, { useState } from 'react';
function App() {
const [time, updateTime] = useState(() => { return 10 });
const [timerRunning, setTimerRunning] = useState(false);
let minutes = Math.floor(time / 60);
let seconds = time % 60;
let interval;
function startTimer() {
setTimerRunning(true);
interval = setInterval( function() {
updateTime(previousTime => previousTime === 0 ? previousTime : previousTime - 1);
}, 1000);
}
function pauseTimer() {
setTimerRunning(false);
clearInterval(interval);
}
function restartTimer() {
setTimerRunning(false);
updateTime(() => {return 10});
}
return (
<>
<p>{minutes > 9 ? minutes : "0" + minutes}:{seconds > 9 ? seconds : "0" + seconds}</p>
<button onClick={startTimer}>Start</button>
<button onClick={pauseTimer}>Pause</button>
<button onClick={restartTimer}>Restart</button>
</>
)
}
export default App;
I want the pause button to pause the timer. Eventually I'll make conditional statements to have each button appear based on the state of the app and the value of time, but the pause button is my current obstacle.
I first had a separate countdown function which used a conditional to stop the time when the time matched counter (below). I thought of a less complicated way that lets me omit the counter variable (above). Im not sure which option is better, or if either is preventing the clearInterval function to work properly. The clearInterval function works within the countdown function if statement, but will not work outside of it.
import React, { useState } from 'react';
function App() {
const [time, updateTime] = useState(() => { return 10 });
const [timerRunning, setTimerRunning] = useState(false);
let counter = 0;
let minutes = Math.floor(time / 60);
let seconds = time % 60;
let interval;
function countdown() {
counter++;
if ( counter === time ) {
setTimerRunning(false);
clearInterval(interval);
}
updateTime(previousTime => previousTime - 1);
}
function startTimer() {
setTimerRunning(true);
interval = setInterval(countdown, 1000);
}
function pauseTimer() {
setTimerRunning(false);
clearInterval(interval);
}
function restartTimer() {
setTimerRunning(false);
updateTime(() => {return 10});
}
return (
<>
<p>{minutes > 9 ? minutes : "0" + minutes}:{seconds > 9 ? seconds : "0" + seconds}</p>
<button onClick={startTimer}>Start</button>
<button onClick={pauseTimer}>Pause</button>
<button onClick={restartTimer}>Restart</button>
</>
)
}
export default App;
Basically you can't create let interval; and assign it a setInterval like interval = setInterval(countdown, 1000);
because on each re-render there will be new let interval;
what you need to do is create a variable which isn't change on re-redners, you can use useRef
const interval = useRef(null);
.....
function startTimer() {
interval.current = setInterval(countdown, 1000);
}
....
function pauseTimer() {
clearInterval(interval.current);
}
and I don't think you need const [timerRunning, setTimerRunning] = useState(false);
find a demo here
Basically when functional component re-renders it will execute from top to bottom, if you use like let counter = 0;, then on each re-render it will initialize to 0, if you need to persists your values in each re-renders you might need some hooks (Ex: useState, useRef ...), in this case useRef would do the trick (because you need only one setInterval in each re-renders and useRef will give you the previous value, it will not re-initalize like a general variable)
You have to use useEffect, like this:
const handleStart = () => {
setChangeValue(true)
}
const handlePause = () => {
setChangeValue(false)
pauseTimer()
}
const handleRestart = () => {
setInitialState()
setChangeValue(true)
}
useEffect(() => {
if (changeValue) {
const interval = setInterval(() => {
startTimer()
}, 100)
return () => clearInterval(interval)
}
}, [changeValue])
you have three buttons to start, pause and restart, invoke these (handleStart, handlePause, handleRestart) functions with them
that is my solution
instead of the startTime function, I use useEffect
useEffect(()=>{
interval = timerRunning && setInterval(() => {
updateTime(previousTime => previousTime === 0 ? previousTime : previousTime - 1);
}, 1000);
return ()=> clearInterval(interval)
},[timerRunning])
and in onClick Start Button
<button onClick={()=> setTimerRunning(true)}>Start</button>
I hope it is useful