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])
Related
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.
const onDrawerClose = () => {
setCloseDrawer1(false);
setCloseDrawer2(false);
setData(null);
};
When some fields in the data are empty I show error message. It works good but when I close the sidebar, while it's closing I can see for 1second or so this error message since here in the code above we set data to null, how I can properly run the third setState after the previous two are completed?
You can listen when the draw1 and draw2 are closed using an useEffect like:
useEffect(() => {
if(!closeDrawer1 && !closeDrawer2) {
setData(null);
}
}, [closeDrawer1, closeDrawer2])
If there is an animation when drawers are closing you can set data after the animation happen using setTimeout.
useEffect(() => {
const drawerCloseTimeout = 1000; // 1 sec
if(!closeDrawer1 && !closeDrawer2) {
const timeout = setTimeout(() => setData(null), drawerCloseTimeout);
return () => clearTimeout(timeout);
}
}, [closeDrawer1, closeDrawer2]);
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.
I just started learning react and I have about a performance question. I want to increment a counter using the setInterval function and then reset it when the length of the array is reached. The problem is I need to add an active variable for the useEffect dependency, which removes the interval and then creates it again.
useEffect(() => {
const timer = setInterval(() => {
active == arr.length - 1 ? setActive(0) : setActive((active) => active + 1)
}, 3000)
return () => clearInterval(timer)
}, [active])
So, I wrote code like this, which looks crazy, but does the same job without removing the interval, and gets the actual version of the active variable from the callback.
useEffect(() => {
const timer = setInterval(() => {
setActive((active) => (active === arr.length - 1 ? active == 0 : active + 1))
}, 3000)
return () => clearInterval(timer)
}, [])
The question is how best to write and not do unnecessary work in the component
I would probably split all this logic out into a couple effects.
One to manage incrementing active on the interval and clearing the interval when the component unmounts.
useEffect(() => {
const timer = setInterval(() => {
setActive(active => active + 1);
}, 3000);
return () => clearInterval(timer);
}, []);
A second to reset the state on the condition.
useEffect(() => {
if (active === arr.length - 1) {
setActive(0);
}
}, [active, arr]);
I would also caution you to protect against the edge case where active is updated to 0 and the arr array is an array of length 1 as this will trigger render looping.
I want to have a countdown timer that counts from 60 to zero when the stage of program reaches two. Here's my code:
const [timer, setTimer] = useState(60)
useEffect(() => {
if (stage === 2) {
const interval = setInterval(() => {
console.log(timer)
setTimer(timer - 1)
if (timer === 1) {
clearInterval(interval)
}
}, 1000)
}
}, [stage])
and i have a div like below that just shows the counter value
<div>{timer}</div>
in my setInterval, when i use console.log(timer) it always prints out 60. But inside the div, the value starts with 60 and in the next second it will always be 59.
What am i doing wrong here?
You have closure on time === 60 value, use functional update instead.
For the same reason, you should have another useEffect for canceling the interval:
const intervalId = useRef();
useEffect(() => {
if (stage === 2) {
intervalId.current = setInterval(() => {
setTimer((prevTimer) => prevTimer - 1);
}, 1000);
}
}, [stage]);
useEffect(() => {
console.log(timer);
if (timer === 1) {
clearInterval(intervalId.current);
}
}, [timer]);
Check similar question: setInterval counter common mistakes.