I'm trying to change the color of a SVG structure from red to white and white to red using React Hooks. The problem is that after a few seconds the color starts changing rapidly instead of every second. I'm not understanding where I'm going wrong in the code.
Here is the useState.
const[rectColor, rectColorSet] = useState('red')
And the useEffect for changing the color.
useEffect(() => {
if(rectColor === 'red'){
let timer = setInterval(() => {
rectColorSet('white')
}, 1000)
}
// console.log('timer',timer)
else if(rectColor ==='white'){
let timer = setInterval(() => {
rectColorSet('red')
}, 1000)
}
})
And this is the place where I use the state which contains color.
d={`
M-0.20 0 L-0.20 0.30 L0.20 0.30 L0.20 0 L-0.20 0
Z`}
fill={props.value ? "green" : rectColor}
/>
}
Every render's setting a new interval. Use setTimeout instead of setInterval, and don't forget to clean up the effects:
useEffect(() => {
if (rectColor === 'red' || rectColor === 'white') {
const timeoutID = setTimeout(() => {
rectColorSet(rectColor === 'red' ? 'white' : 'red');
}, 1000);
return () => clearTimeout(timeoutID);
}
});
Or toggle between a boolean instead of using a string for state. If you add other state that might change, using a single interval when the component mounts might be easier to manage:
const [red, setRed] = useState(true);
useEffect(() => {
const intervalID = setInterval(() => {
setRed(red => !red);
}, 1000);
return () => clearInterval(intervalID);
}, []);
After a few seconds, the color starts changing rapidly instead of every second because you put [] in useEffect.
If you want to run an useEffect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as the second argument in useEffect hook.
And You should use setTimeout() intend of setInterval()
So you should write your useEffect like this:
const [red, setRed] = useState(true);
useEffect(() => {
const intervalID = setTimeout(() => {
setRed(red => !red);
}, 1000);
return () => clearInterval(intervalID);
}, []);
You actually need to replace setInterval() with setTimeout()
and rather than declaring everthing seperately use OR in the conditional and clean up the code.
Related
const [panelRight, setPanelRight] = useState(sample(filter(menu, (el) => el !== panelLeft)));
useEffect(() => {
const interval = setInterval(() => {
const menuCategory = filter(menu, (el) => el !== panelLeft && el !== panelRight);
setPanelRight(sample(menuCategory));
}, 10000);
Currently panelRight is being set to the same random menu element every time the the setInterval is called. I think this might be because I need to reference the previous state in the filter method, but how?
Your code lacks details, we dont know what is in your useEffect deps so lets assume and guess. Provide more details next time, please.
Lets assume your useEffect has an empty [] dependencies array and called only once on the first render.
So the issue - setInterval will always hold the value it had initially when the setInterval callback (closure) was defined. Values from the first render.
And here is what you should do:
// Insert that before or after your setStates section
const panelRightRef = React.useRef();
const panelLeftRef = React.useRef();
// That will update ref variables
useEffect(() => {panelRightRef.current = panelRight}, [panelRight]);
useEffect(() => {panelLeftRef.current = panelLeft}, [panelLeft]);
useEffect(() => {
const interval = setInterval(() => {
// if menu is also a State variable and is not immutable - you should add useRef() for it
const menuCategory = filter(menu, (el) => el !== panelLeftRef.current && el !== panelRightRef.current);
setPanelRight(sample(menuCategory));
}, 10000);
// that will be called on depsarray changes or when component is destroyed
return () => clearInterval(interval);
}, []);
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'm currently trying to create a stopwatch component using React Native and the setInterval function to increment a counter and set the new value to state like so:
Play Function(this runs after hitting the play button)
const [isRunning, setisRunning] = useRecoilState(runState);
const [time, setTime] = useRecoilState(timeState);
const [timerRef, setTimerRef] = useState(0);
const Play = useCallback(() => {
window.clearInterval(interval); //clear the interval
if (isRunning === false) { //set the isRunning state to true
setisRunning((isRunning) => !isRunning);
interval = window.setInterval(() => { //setInterval operation
setTime((previousTime) => previousTime + 1);
}, 100);
setTimerRef(interval); //setTimer reference to pause
}
}, [time]);
Pause Function(this runs after hitting the pause button)
const Pause = () => {
if (isRunning === true) {
setisRunning((isRunning) => !isRunning);
window.clearInterval(timerRef);
}
};
My problem is that the timer tends to speed up erratically an as such I'm not able to use the time value passed. I'm not sure where I am going wrong, kindly assist.
Ok so Iv've run into this quite recently in my next.js project. The correct way to do it seems like something like that:
useEffect(() => {
const interval = setInterval(() => {
console.log('This will run every second!');
}, 1000);
return () => clearInterval(interval);
}, []);
See for example here
useEffect(() => {
const id_1 = setTimeout(() => {
// do something
clearTimeout(id_1);
}, 500);
}, [success_failure_msg[0]]);
vs
useEffect(() => {
const id_1 = setTimeout(() => {
// do something
}, 500);
return () => clearTimeout(id_1);
}, [success_failure_msg[0]]);
What is the difference and which is the correct practice?
The correct practice is the second one that you provided. To answer your question you really have to understand why we clear the timeout.
Let's take an example of a simple react component:
const Comp = () => {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
setCount(1); // set the count in the timeout
}, 1000);
}, [setCount]);
return (
<div>count: {count}</div>
);
}
Now this will work as long as the component is rendered. But what happens if the component gets unmounted before the timeout is resolved. The timeout will still run and you will be calling setCount of a component that is no longer in the view.
If you change it to the code you have given in your first example the problem is still the same:
useEffect(() => {
const id_1 = setTimeout(() => {
clearTimeout(id1); // this does not do anything as the timeout has already run
setCount(1); // set the count in the timeout
}, 1000);
}, [setCount]);
The clearing the timeout does not do anything and you will still have a memory leak. The only way to resolve it is to return a function from the useEffect that react will run when it unmounts the component. Within that function you can clean up the code.
useEffect(() => {
const id_1 = setTimeout(() => {
setCount(1); // set the count in the timeout
}, 1000);
// React will run this function when it unmounts and so no memory leak
return () => clearTimeout(id_1)
}, [setCount]);
both seems wrong to me, cleartimeout should be before settimetimeout
and when success_failure_msg changes two time the settimeout trigger only once.
Example
var id_1 = undefined;
useEffect(() => {
clearTimeout(id_1)
id_1 = setTimeout(() => {
success_failure_msg // shoule be test2
}, 500);
}, [success_failure_msg]);
success_failure_msg = "test";
success_failure_msg = "test2"
I create simple custom hook that save the screen height and width .
The problem is that I want to re-render(update state) only if some condition in my state is happened and not in every resize event..
I try first with simple implementation :
const useScreenDimensions = () => {
const [height, setHeight] = useState(window.innerWidth);
const [width, setWidth] = useState(window.innerHeight);
const [sizeGroup, setSizeGroup]useState(getSizeGroup(window.innerWidth));
useEffect(() => {
const updateDimensions = () => {
if (getSizeGroup() !== sizeGroup) {
setSizeGroup(getSizeGroup(width));
setHeight(window.innerHeight);
setWidth(window.innerWidth);
}
};
window.addEventListener('resize', updateDimensions);
return () => window.removeEventListener('resize', updateDimensions);
}, [sizeGroup, width]);
return { height, width };
}
The problem with this approach is that the effect calls every time , I want that the effect will call just once without dependencies (sizeGroup, width) because I don't want to register the event every time there is a change in screen width/size group(window.addEventListener).
So, I try with this approach with UseCallBack , but also here my 'useEffect' function called many times every time there is any change in the state..
//useState same as before..
const updateDimensions = useCallback(() => {
if (getSizeGroup(window.innerWidth) !== sizeGroup) {
setSizeGroup(getSizeGroup(width));
setHeight(window.innerHeight);
setWidth(window.innerWidth);
}
}, [sizeGroup, width]);
useEffect(() => {
window.addEventListener('resize', updateDimensions);
return () => window.removeEventListener('resize', updateDimensions);
}, [updateDimensions]);
....
return { height, width };
The question is what the correct and effective way for my purposes? I want just "register" the event once, and update the my state only when my state variable is true and not every time the width or something else get updated..
I know that when you set empty array as second argument to 'UseEffect' it's run only once but in my case I want that the register of my event listener run once and on resize I will update the state only if some condition is true
Thanks a lot.
use 2 different useEffect
first one for register event.So below code will run at the time of componentDidMount.
useEffect(() => {
window.addEventListener('resize', updateDimensions);
}, []);
second useEffect to run based on state change.
useEffect(() => {
updateDimensions();
return () => window.removeEventListener('resize', updateDimensions);
}, [sizeGroup, width])
const updateDimensions = useCallback(() => {
setSizeGroup(getSizeGroup(width));
setHeight(window.innerHeight);
setWidth(window.innerWidth);
}
I'm not sure useCallback function need to use or not. And I've not tested this code.