JavaScript On-click Function to start and stop Intervals - javascript

I have two onclick functions that performing start and stop functions using socket when i click start it start requesting images from server(i am using intervals) when i click stop the server is stopped but the request keep coming to the server. the intervals seems not stopped after clicking kindly review my code
Code:
var interval;
function onclick() {
interval = setInterval(() => {
socket.emit("get_image", "Click");
socket.on('send_image', (message) => {
setImage(message.image);
});
}, 350)
}
function onclicks() {
clearInterval(interval);
setImage(blanked);
}
I have tried to add clearInterval function but it seems not working

Variables declared at the top level of a component are redeclared on each render (here every time you call setImage()) so interval is undefined by the time onclicks() is called.
In order to maintain the value through renders you need to either use state (useState) or a ref (useRef) which won't lose it's value. In this case, since you don't want renders attached to the value of interval a ref is more appropriate. useRef() returns an object with a current property in which your value will be stored.
const interval = React.useRef(null);
function onclick() {
interval.current = setInterval(() => {
socket.emit("get_image", "Click");
socket.on('send_image', (message) => {
setImage(message.image);
});
}, 350);
}
function onclicks() {
clearInterval(interval.current);
interval.current = null; // reset the ref
setImage(blanked);
}
You probably also want to avoid setting multiple intervals. You may already be doing so, but if not you can check if interval is null or not before setting a new one.
const interval = React.useRef(null);
function onclick() {
if (interval.current === null) { // only set new interval if interval ref is null
interval.current = setInterval(() => {
socket.emit("get_image", "Click");
socket.on('send_image', (message) => {
setImage(message.image);
});
}, 350);
}
}

For me, the easiest way to solve this behavior was set the interval as a Ref:
import { useRef } from 'react';
// Initialize the interval ref
const interval = useRef();
// Create the interval
function onclick() {
interval.current = setInterval(() => {
socket.emit("get_image", "Click");
socket.on('send_image', (message) => {
setImage(message.image);
});
}, 350)
}
// Remove the interval
function onclicks() {
clearInterval(interval.current);
setImage(blanked);
}
Using this hook, the component will not re-render when you change the value remove/change the interval.

Related

set time dynamic time for set interval in react

Hello i am struggling to set dynamic time for settimeout function in react js.
i have long string of key value pair of time and message. i wants to display each message for specific time and loop through whole list.
here is what i am trying, but not working.
const [timer, setTimer] = useState(0)
const [time, setTime] = useState(5000)// this is default value to start which need to update with str time value
const str=[{name:"rammy", time:1000},
{name:"james", time:4000},
{name:"crown", time:2000}]
useEffect(()=>{
const getTime= str[timer].time
setTime(getTime)
},[timer])
//when timer change it should update update time state which will be used to update time for time settime out
function increment() {
useEffect(()=>{
setTimeout(() => {
setTimer((ele)=>ele+1)
}, time);
},[timer])
} // above code is for increment time state on each iteration
function ButtonHandle(){
//setRealString(itr)
increment()
} //button handler for start timer
First of all, you can't put hooks inside functions (other than your component functions). https://reactjs.org/docs/hooks-rules.html
So take the useEffect out of increment()
useEffect(()=>{
increment()
},[timer])
function increment() {
setTimeout(() => {
setTimer((ele)=>ele+1)
}, time);
}
But you also need to clear the timeout. We can return the timeout function to reference it, and clear the time out with a return inside useEffect. Clearing timeouts and intervals in react
useEffect(()=>{
const myTimeout = increment()
return () => {
clearTimeout(myTimeout)
}
},[timer])
function increment() {
return setTimeout(() => {
setTimer((ele) => ele + 1);
}, time);
}
Then we can combine the useEffects which both have a dependancy array of [timer].
useEffect(() => {
const getTime = str[timer].time;
setTime(getTime);
const myTimeout = increment();
return () => {
clearTimeout(myTimeout);
};
}, [timer]);
You don't need to use useEffect to do it. You misunderstood the useEffect usage, it's a react hook to you implement side-effects and you can't use react-hooks inside a function, it should be in the component scope.
I can increment directly from the ButtonHandle function.
// On the index state is necessary in this implementation
const [index, setIndex] = useState(-1)
const guys=[
{name: "rammy", time:1000},
{name: "james", time:4000},
{name: "crown", time:2000}
]
// useCallback memoize the increment function so that it won't
// change the instance and you can use it in the useEffect
// dependency array
const increment = useCallback(() => {
setIndex((i) => i+1)
}, [])
useEffect(() => {
// change the state if the timer is greater than -1
if (index !== -1) {
if (index >= guys.length) {
setIndex(-1);
} else {
setTimeout(() => {
increment();
}, guys[index].time); // <-- you get the time from your array
}
}
}, [index, increment]);
function handleClick(){
//setRealString(itr)
increment()
}
Even though I helped you, I don't know what you're trying to do. This implementation sounds like a code smell. We can help you better if you explain the solution you're trying to do instead of just the peace of code.
You don't need to set the time state, as you already have the time in the array; avoiding unnecessary state changes is good.

React won't reload function despite state change

I have a state that tracks the window width:
const [innerWidth, setInnerWidth] = useState(window.innerWidth)
In useEffect, I create a resize eventListener which sets the state to the new width:
useEffect(() => {
document.addEventListener('resize', () => {
setInnerWidth(window.innerWidth)
})
}, [])
Lastly, I have a function test that logs the innerWidth every 5 seconds, with an interval started in useEffect
function test() {
console.log(innerWidth)
}
useEffect(() => {
setInterval(test, 5000)
}, [])
Unfortunately, despite any resize that happen, the test() function keeps on logging the original innerWidth value.
How can I tell react to reload the test function as well?
EDIT:
The perpetual log of the innerWidth was just a simplification of my actual use case. Actually, the timer is shifting an element on the x-axis, and I need to know when it exceeds the width to stop the execution and start again.
Creating and invalidating a loop every time the window changes, like in several answers you've given, temporarily stops the shifting of my element, as the loop gets invalidated. I would like to avoid this.
The useEffect created a closure around the original values, so that's all it ever logs. You'd need the effect to update any time the value changes, by adding it to the dependency array:
useEffect(() => {
setInterval(test, 5000)
}, [innerWidth])
This would of course create a new interval on every state change. So the useEffect should return a function which cancels the interval:
useEffect(() => {
const x = setInterval(test, 5000);
return () => clearInterval(x);
}, [innerWidth])
That way there's only one interval running at any given time.
Though this begs the question... Why? If the goal is to log the value of innerWidth to observe its changes, then why re-log the same value every 5 seconds indefinitely? Skip the test function and the interval entirely and just log the value any time it changes:
useEffect(() => {
console.log(innerWidth);
}, [innerWidth])
Can you change the test function to an anonymous function?
const test = () => {
console.log(innerWidth);
};
Change you useEffect:
useEffect(() => {
window.addEventListener('resize', () => {
setInnerWidth(window.innerWidth);
});
}, [setInnerWidth]);
The solution was wrapping the innerWidth into an object, so that it is passed by reference and it 'updates' in the test function.
const innerWidthWrapper = {width: window.innerWidth}
useEffect(() => {
innerWidthWrapper.width = window.innerWidth
})
}, [])
Edit: Your issue using the interval function is explained in this answer
This code works for me by logging the state variable using the effect hook:
const [innerWidth, setInnerWidth] = useState(window.innerWidth);
useEffect(() => {
const updateWidth = () => {
setInnerWidth(window.innerWidth);
};
window.addEventListener("resize", updateWidth);
}, []);
useEffect(() => {
console.log(innerWidth);
}, [innerWidth]);
I added the eventlistener using the window object.
Sandbox

Same logic but different behaviour in 'class' and in 'functional component'

Attempted to translate an example code from class to functional component and faced the problem.
the target file is in components/Wheel/index.js
Key function that causes problem
const selectItem = () => {
if (selectedItem === null) {
const selectedItem = Math.floor(Math.random() * items.length);
console.log(selectedItem);
setSelectedItem(selectedItem);
} else {
setSelectedItem(null);
let t= setTimeout(() => {
selectItem()
}, 500);
clearTimeout(t);
}
};
First time is normal,
from second time onward,
2 clicks are needed for the wheel to spin.
I had to add clearTimeout() or infinite loop is resulted, but the same does not happen in the original.
Original working example in class
My version in functional component.
MyVersion
Thank you.
What an excellent nuance of hooks you've discovered. When you call selectItem in the timeout, the value of selectedItem that is captured in lexical scope is the last value (not null).
There's two answers, a simple answer and a better working answer.
The simple answer is you can accomplish it be simply separating the functions: https://codesandbox.io/s/spinning-wheel-game-forked-cecpi
It looks like this:
const doSelect = () => {
setSelectedItem(Math.floor(Math.random() * items.length));
};
const selectItem = () => {
if (selectedItem === null) {
doSelect();
} else {
setSelectedItem(null);
setTimeout(doSelect, 500);
}
};
Now, read on if you dare.
The complicated answer fixes the solution for the problem if items.length may change in between the time a timer is set up and it is fired:
https://codesandbox.io/s/spinning-wheel-game-forked-wmeku
Rerendering (i.e. setting state) in a timeout causes complexity - if the component re-rendered in between the timeout, then your callback could've captured "stale" props/state. So there's a lot going on here. I'll try and describe it as best I can:
const [selectedItem, setSelectedItem] = useState(null);
// we're going to use a ref to store our timer
const timer = useRef();
const { items } = props;
// this is just the callback that performs a random select
// you can see it is dependent on items.length from props
const doSelect = useCallback(() => {
setSelectedItem(Math.floor(Math.random() * items.length));
}, [items.length]);
// this is the callback to setup a timeout that we do
// after the user has clicked a "second" time.
// it is dependent on doSelect
const doTimeout = useCallback(() => {
timer.current = setTimeout(() => {
doSelect();
timer.current = null;
}, 500);
}, [doSelect]);
// Here's the tricky thing: if items.length changes in between
// the time we rerender and our timer fires, then the timer callback will have
// captured a stale value for items.length.
// The way we fix this is by using this effect.
// If items.length changes and there is a timer in progress we need to:
// 1. clear it
// 2. run it again
//
// In a perfect world we'd be capturing the amount of time remaining in the
// timer and fire it exactly (which requires another ref)
// feel free to try and implement that!
useEffect(() => {
if (!timer.current) return;
clearTimeout(timer.current);
doTimeout();
// it's safe to ignore this warning because
// we know exactly what the dependencies are here
}, [items.length, doTimeout]);
const selectItem = () => {
if (selectedItem === null) {
doSelect();
} else {
setSelectedItem(null);
doTimeout();
}
};

React native, how to stop infinitely running setInterval when a particular state variable value changes

In my react native project I need to create a blinking (like warning) text. For this I use setInterval inside useffect() where I switch the text visibility by using setShowText((showText) => !showText). Now I need to to stop the setInterval when another state variable stopTriger changes to true. (I have a sperate method to set setStopTriger). Now the situation is like console.log('timer is running infinitely'). How can I make it to work? I use the following code, but not sure where to keep stopTriger condition:
const [stopTriger, setStopTriger] = useState(false)
const [showText, setShowText] = useState(true);
useEffect(() => {
// Change the state every second
const interval = setInterval(() => {
setShowText((showText) => !showText);
console.log('timer is running infinitely ')
// need to stop timer when stopTriger == true , how do I do it ?
}, 1000);
return () => clearInterval(interval);
}, []);
You can put intervalID in a state, and use another useEffect with stoTrigger as a dependency, when that variable is true, you can clear the interval:
Update
const [interval, setStateInterval] = useState(0);
useEffect(() => {
// Change the state every second
const interval_ = setInterval(() => {
/* set value of interval state */
setStateInterval(interval_);
setShowText(showText => !showText);
console.log('timer is running infinitely ');
// need to stop timer when stopTriger == true , how do I do it ?
}, 1000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
if (stopTrigger) clearInterval(interval);
}, [stopTriger]);
Also, clearInterval() will automatically take care if the interval is currently running before clearing it, so you don't need to worry
stopTriger is initially false, and you aren't modifying it.

setTimeout() function is not detecting a state change and keeps executing the recursive function

useEffect(() => {
playLoop();
}, [state.playStatus]);
const playLoop = () => {
if (state.playStatus) {
setTimeout(() => {
console.log("Playing");
playLoop();
}, 2000);
} else {
console.log("Stopped");
return;
}
};
Output:
Stopped
// State Changed to true
Playing
Playing
Playing
Playing
// State Changed to false
Stopped
Playing // This is the problem, even the state is false this still goes on execute the Truthy stalemate
Playing
Playing
I am working on react-native and I want the recursion to stop when the state value becomes false.
Is there any other way I can implement this code I just want to repeatedly execute a function while the state value is true.
Thank you
Rather than having a playStatus boolean, I'd save the interval ID. That way, instead of setting playStatus to false, call clearInterval. Similarly, instead of setting playStatus to true, call setInterval.
// Can't easily use useState here, because you want
// to be able to call clearInterval on the current interval's ID on unmount
// (and not on re-render) (interval ID can't be in an old state closure)
const intervalIdRef = useRef(-1);
const startLoop = () => {
// make sure this is not called while the prior interval is running
// or first call clearInterval(intervalIdRef.current)
intervalIdRef.current = setInterval(
() => { console.log('Playing'); },
2000
);
};
const stopLoop = () => {
clearInterval(intervalIdRef.current);
};
// When component unmounts, clean up the interval:
useEffect(() => stopLoop, []);
The first thing you should do is make sure to clear the timeout when the state changes to stopped or otherwise check the state within the timeout callback function.
But the problem does not seem to be with the setTimeout code only by itself, but rather that this playLoop is also being called too many times. You should add a console.log with a timestamp right at the start of your playLoop to confirm or disprove this. And to find out where it is called from, you could use console.trace.
const playLoop = () => {
console.log(new Date(), ': playLoop called')
console.trace(); // optional
if (state.playSt....

Categories

Resources