How can I make state updating asynchronous in React+Redux - javascript

I'm writing an incremental-style game in React and I have this setInterval inside App.ts:
useEffect(() => {
const loop = setInterval(() => {
if (runStatus) {
setTime(time + 1);
}
}, rate);
return () => clearInterval(loop);
});
I've also got various event handlers along the lines of:
<button onClick={() => increment(counter + 1)}>Increment</button>
Unfortunately, if I click that button multiple times in a second, it will block the setInterval from updating the time state.
This state using the hooks as well as Redux stored state.
How can I, at the very least, make my setInterval tick reliably, so that I can click around the page without blocking it?

Do it like that
[] empty dependency means only execute when a component will mount.
useEffect(() => {
const loop = setInterval(() => {
if (runStatus) {
setTime(prev => prev + 1);
}
}, rate);
return () => clearInterval(loop);
}, []); <--- add that []
Notes
adding time as a dependency will create an infinite loop
you need to add runStatus or rate variable as a dependency if its dynamic state

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.

How setInterval works in the background of React Re-Rendering

I'm creating a simple countdown timer app with React and I am having hard time understanding how setInterval works while React re-renders the component.
For example, this following code had timer continue to run even though I had used clearInterval onPause().
let startTimer;
const onStart = () => {
startTimer = setInterval( ()=>{
if ( timeRemaining === 0 ) {
clearInterval(startTimer);
setIsCounting(false)
return
}
updateTimer()
}, 1000)
setIsCounting( (prev) => !prev )
} // end of onStart
const onPause = () => {
setIsCounting( (prev) => !prev )
clearInterval(startTimer)
}
return (
{ props.isCounting ?
<button onClick={props.onPause}> Pause </button>
: <button onClick={props.onStart}> Start </button> }
)
However, the timer successfully pauses when I simply change
let starter;
to
let startTimer = useRef(null)
const onStart = () => {
startTimer.current = setInterval( ()=>{
if ( timeRemaining === 0 ) {
clearInterval(startTimer);
setIsCounting(false)
return
}
updateTimer()
}, 1000)
setIsCounting( (prev) => !prev )
} // end of onStart
const onPause = () => {
setIsCounting( (prev) => !prev )
clearInterval(startTimer.current)
}
What's happening to setInterval when React re-renders its component? Why did my timer continue to run when I didn't use useRef()?
A ref provides what's essentially an instance variable over the lifetime of a component. Without that, all that you have inside an asynchronous React function is references to variables as they were at a certain render. They're not persistent over different renders, unless explicitly done through the call of a state setter or through the assignment to a ref, or something like that.
Doing
let startTimer;
const onStart = () => {
startTimer = setInterval( ()=>{
could only even possibly work if the code that eventually calls clearInterval is created at the same render that this setInterval is created.
If you create a variable local to a given render:
let startTimer;
and then call a state setter, causing a re-render:
setIsCounting( (prev) => !prev )
Then, due to the re-render, the whole component's function will run again, resulting in the let startTimer; line running again - so it'll have a value of undefined then (and not the value to which it was reassigned on the previous render).
So, you need a ref or state to make sure a value persists through multiple renders. No matter the problem, reassigning a variable declared at the top level of a component is almost never the right choice in React.

SetInterval causes too many re-renders React

Hello I would like to put setInterval in my React project to add 1 for each second but I got an error like in title of this post.
js:
const [activeTab, setActiveTab] = useState(0)
useEffect(() => {
setInterval(setActiveTab(prevTab => {
if (prevTab === 3) return 0
console.log('hi')
return prevTab += 1
}), 1000)
})
There are a few issues:
You're not passing a function to setInterval, you're calling setActiveTab and passing its return value into setInterval.
If you were passing in a function, you'd be adding a new repeated timer every time your componennt ran. See the documentation — useEffect:
By default, effects run after every completed render...
And setInterval:
The setInterval() method... repeatedly calls a function or executes a code snippet, with a fixed time delay between each call.
(my emphasis)
Starting a new repeating timer every time your component re-renders creates a lot of repeating timers.
Your component will leave the interval timer running if the component is unmounted. It should stop the interval timer.
To fix it, pass in a function, add a dependency array, and add a cleanup callback to stop the interval:
const [activeTab, setActiveTab] = useState(0);
useEffect(() => {
const handle = setInterval(() => { // *** A function
setActiveTab(prevTab => {
if (prevTab === 3) return 0;
console.log("?ghi");
return prevTab += 1;
});
}, 1000);
return () => { // *** Clear the interval on unmount
clearInterval(handle); // ***
}; // ***
}, []); // *** Empty dependency array = only run on mount
Side note: Assuming you don't need the console.log, that state setter callback function can be simpler by using the remainder operator:
setActiveTab(prevTab => (prevTab + 1) % 3);
useEffect(() => {
setInterval(() => {
setActiveTab((prevTab) => {
if (prevTab !== 3) {
return (prevTab += 1);
} else {
return 0;
} // console.log("hi");
});
}, 1000);
}, []);

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.

Lodash _.debounce is not canceling existing timer when function is called again in React

I'm trying to debounce the onChange event for a form in my React component. I plan on moving debounceStateUpdate into a static utils function to universalize the debounce time, which is why that extra layer exists rather than just directly using _.debounce.
const ProfileGeneralEditContent = props => {
const debounceStateUpdate = updateFunction => {
return _.debounce(params => updateFunction(params), 700);
};
const FormsyFieldUpdated = debounceStateUpdate((config) => {
console.log("update some things here");
});
return (
<Formsy
onChange={(config) => {
FormsyFieldUpdated.cancel();
FormsyFieldUpdated(config);
}}
onInvalid={() => setValid(false)}
onValid={() => setValid(true)}
>
<div className={'flex justify-start items-start'}>
.
.
. (more jsx)
I would think that when the onChange event fires, the cancel() call would cancel any existing debounce timers that are running and start a new one.
My goal is to debounce inputs from updating state on each key press, so that state will only update after 700ms of no updates. But currently, this code is only delaying each key press' state update by 700 milliseconds, and the state updates for each key press is still happening.
How do I use _.debounce to keep a single running debounce timer for delaying my state update, rather than having 10 timers running at once for each key that is pressed?
I figured it out. I needed to wrap my debounced function definition in useCallback(), because the re-rendering of the component was redefining the debounced function every keypress and thus it would have no knowledge of its previous iterations' running functions.
const ProfileGeneralEditContent = props => {
const debounceStateUpdate = updateFunction => {
return _.debounce(params => updateFunction(params), 700);
};
const FormsyFieldUpdated = useCallback(debounceStateUpdate((config) => {
console.log("update some things here");
}), []);
return (
<Formsy
onChange={(config) => FormsyFieldUpdated(config)}
onInvalid={() => setValid(false)}
onValid={() => setValid(true)}
>
<div className={'flex justify-start items-start'}>
.
.
. (more jsx)

Categories

Resources