React-- why do looped state updates precede recursive state updates? - javascript

When you click the start button, it starts two counters. One uses recursion and the other uses a for loop. Why does the looping counter block the counting of the recursive counter? Also, why is there a limit for the number of iterations in the for loop?
import React from 'react'
export default class Clock extends React.Component {
state = {
recursiveCount: 0,
loopCount: 0,
stop: false
}
clickStart = () => {
this.setState({
stop: false
}, () => {
this.startRecursion()
this.startLooping()
})
}
startLooping = () => {
for (let i = 0; i < 1000; i++) {
setTimeout(() => {
this.setState({
loopCount: this.state.loopCount + 1
})
}, 1)
}
}
stop = () => {
this.setState({
stop: true
})
}
startRecursion = () => {
if (!this.state.stop) {
setTimeout(() => {
this.setState({
recursiveCount: this.state.recursiveCount + 1
}, () => {
this.startRecursion()
})
}, 1)
}
}
render () {
return (
<div>
<button className = 'button' onClick =
{this.clickStart}>Start</button>
<button className = 'button' id = 'stop' onClick =
{this.stop}>Stop</button>
<h1>The recursive function has been recursively called {this.state.recursiveCount} times</h1>
<h1> The iterative function has been iteratively called {this.state.loopCount} times </h1>
</div>
)
}
}

Lets step through the code:
for (let i = 0; i < 1000; i++) {
It loops 1000 times. Thats much and will probably hang the browser for a bit.
setTimeout(/*callback*/, 1);
You add a lot of timeouts to the queue. Now the code is done. One tick later, all timeouts fire at the same time, and will be executed directly one after another. Thats why the counter directly jumps from 1000 to 2000.
Why does the looping counter block the counting of the recursive counter?
Cause there is only one thread, and that will be blocked by the 1000 callbacks that all get into the threads queue at the same time. The recursive timeout will also end in the queue, therefore it will only be executed after the 1000 others, which takes time.
Also, why is there a limit for the number of iterations in the for loop?
There is no number of limitations in a loop, however if you block the thread for too long it crashes to help the client:
while(true );
Additionally as chris pointed out, calling setState too often in a loop might get you in trouble.

I don't think you want
for (let i = 0; i < 1000; i++) {
setTimeout(() => {
this.setState({
loopCount: this.state.loopCount + 1
})
}, 1)
}
as that queues 1000 timeouts without waiting for the previous timeout to finish. And, additionally, is blocking.
Here's a working codesandbox with what I think you're going for: https://codesandbox.io/s/2xy00529jp
I took a little bit of a different approach - I'm keeping track of the timeouts and clearing them on stop - I don't think there's anything wrong with using a flag (this.state.stop), but I find my way a little cleaner. Additionally, I'm using the callback for of this.setState - this is super important when doing a large number of sequential state updates where the new state value relies on the previous state value. Take a look through this page for more info.

Related

How to create a countdown timer for each item in an array?

In my React app, I have a list of orders which are supposed to be shown to the user for only 30 seconds so each order has a value of 30 seconds for its duration propery:
[
{
...,
...,
duration: 30
},
{
...,
...,
duration: 30
},
...
]
I'm using Redux Toolkit to store their data so that I can render the UI of these items in various components. I tried to create an action which gets dispatched every 1 second to decrement the duration by one:
decrementCountdown: (state, action) => {
const order = state.entities[action.payload];
if (order) order.duration -= 1;
}
Then, in App.jsx, I dispatch the action using setInterval inside a loop:
useEffect(() => {
let countdown;
for (order of orders) {
// Run until the duration reaches zero
if (order.duration > 1) {
countdown = setInterval(() => dispatch(decrementCountdown(order?.id)), 1000);
}
}
return () => clearInterval(countdown);
}, [orders])
The challenging part is that the timers have to be synched so that everywhere that the items are shown, the same remaining time is shown and decremented.
The method I have used didn't help me much. Especially when more that one order was present. In that case, the new order's duration wouldn't decrement and it caused an infinite loop inside the useEffect.
Is there any way I can create a countdown for each one?
Do you really need to keep countdown in store? It is very bad idea, because every second u will trigger rerenders of components that using this data.
Maybe u can create an CountDown component?
function CountDown(props) {
const [counter, setCounter] = useState(+props.duration);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter - 1);
if (counter <= 0) {
clearInterval(interval);
props.onEnd?.(); // here u can pass any actions after countDown is ended
}
}), 1000);
return () => clearInterval(interval);
}, []);
return <div>{props.counter}</div>
}
But if u need to keep countdowns in redux i recommend you to move it from orders objects and place it to array for example. In this array u will be able to keep counters and intervals for every order.

How can I make state updating asynchronous in React+Redux

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

How to count up by 1 and setState every 5 seconds in react

I have a useState variable that I want to update every 5 seconds by an increment of 1, however I want it to stop counting and start at 0 again once it reaches the array's length. Here is what I have so far. Right now the console logs are confusing, it starts to work and then the numbers just start outputting every second or faster. This works when I dont run the SetShowNum in the increment function, but that's the functionaility I need.
Any help would be greatly appreciated. Thanks
const [showNum, SetShowNum] = useState(0)
useEffect(() => {
var i = 0
var interval = setInterval(increment, 5000)
var stop = data.allSanityProducts.edges.length // right now equates to 4
function increment() {
i = i + 1
if (i === stop) {
i = 0
}
console.log(i)
SetShowNum(i)
}
})
In this solution, I added a dependency array as a second argument for the useEffect. This ensures that useEffect only runs when the component first mounts. Otherwise, the useEffect as you wrote it will keep running on each re-render so that is why you're seeing the memory leakage and all the log statements. I threw in log statements in the useEffect to observe this behavior. I would recommend running the code with and without the dependency array to observe the re-rendering occur.
The React documentation:
"If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works."
const Component = () => {
console.log("rerender");
const [showNum, SetShowNum] = useState(0);
useEffect(() => {
console.log("running useEffect");
var i = 0;
var interval = setInterval(increment, 5000);
// var stop = data.allSanityProducts.edges.length // right now equates to 4
const stop = 4;
function increment() {
i = i + 1;
if (i === stop) {
i = 0;
}
console.log(i);
SetShowNum(i);
}
}, []);
return (...)
};
Link to React useEffect docs:
https://reactjs.org/docs/hooks-effect.html
Link to a codesandbox:
https://codesandbox.io/s/nameless-sunset-1qyq4?file=/src/SO.js:0-590

May the actual delay of setTimeout be shorter?

According to documentation the actual delay of setTimeout may be longer than was asked. Could you please point me on documentation or question with answer which explains may the actual delay of setTimeout be shorter than was asked?
Thing is I encountered an issue which happened really seldom and could be explained by such phenomenon. Platforms: Chrome Version 67, NodeJS Version 9.8.0. Also, I am really curious is this statement true for Firefox and other browsers?
You can test a timeout or interval accuracy by taking a delta of two microtimes. In my tests, the timers are plus or minus a few milliseconds. Run the program below to see the results in your own environment.
In a perfect world, the output would always show 1000. The variance in the output means our world is imperfect 😂
var last = Date.now()
var interval = setInterval(function() {
var now = Date.now()
var delta = now - last
console.log(delta)
last = now
}, 1000)
setTimeout(clearInterval, 10000, interval)
// 1000
// 1003
// 998
// 1002
// 999
// 1007
// 1001
// ...
To dramatically affect the result, press Run, switch to another tab, then come back to this tab after a few seconds. You'll see de-focused tabs have extremely high variance.
// 1004 <-- start experiment
// 997
// 1000 <-- switch to another tab
// 1533 <-- variance spikes immediately
// 866
// 1033
// 568 <-- switch back to this tab
// 1001 <-- variance restabilizes
// 1000
// 999
I don't know all of the things that play a role in affecting the accuracy of timeouts and intervals in JavaScript, but I also don't think that's an important thing to know. Ultimately we don't need accuracy because we can calculate precise durations of time using the delta technique above.
a practical example in React
Below we make a simple Timer component which naively uses setInterval to refresh the timer's display once per second...
class Timer extends React.Component {
constructor (props) {
super (props)
this.state = { seconds: 0, timeout: null }
}
componentDidMount () {
this.setState ({
timeout: setInterval (this.tick.bind(this), 1000)
})
}
componentWillUnmount () {
clearTimeout (this.timeout)
}
tick () {
this.setState ({ seconds: this.state.seconds + 1 })
}
render () {
return <div>Naive timer: {this.state.seconds}</div>
}
}
ReactDOM.render
( <Timer />
, document.getElementById ('timer')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="timer"></div>
But due to the unreliable nature of JavaScript's timers, we know that our Timer component will eventually display the incorrect value.
When we implement PreciseTimer below, we can use our delta technique to ensure the component always displays the correct duration
class PreciseTimer extends React.Component {
constructor (props) {
super (props)
this.state = { start: Date.now (), seconds: 0, timeout: null }
}
componentDidMount () {
this.setState ({
timeout: setInterval (this.tick.bind(this), 1000)
})
}
componentWillUnmount () {
clearTimeout (this.timeout)
}
tick () {
const delta = Date.now () - this.state.start
this.setState ({ seconds: delta / 1000 })
}
render () {
return <div>Precise timer: {this.state.seconds}</div>
}
}
ReactDOM.render
( <PreciseTimer />
, document.getElementById ('timer')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="timer"></div>
To see the practical difference in how these two timers behave, start both of them, switch to a new tab for 10-15 seconds, then switch back to this tab. The naive Timer will suffer from JavaScript timer variance whereas PreciseTimer will always display the correct duration.
Actually setTimeout will just push the callback on top of the event loop after the specified delay, but because Javascript is mono process if something block the event loop the callback will be delayed. But it can't be shorter or it's a bug in the code / javascript engine used.
Javascript's single execution thread means that sometimes things that you queue up to be executed in the future at point x, might actually be executed on time x+, since there might be no execution-time-frame available.
However things will never be able to execute BEFORE the required time, only at-or-after your given time
A setTimeout(myFunc,100) for example may execute after 100 ms, or a little bit longer.
You can find more information on this article regarding javascrip's timers in general:https://johnresig.com/blog/how-javascript-timers-work/
To complete user633183's answer, here is a timer that ensures your function never runs before given time interval:
class Timer {
constructor() {
this.last = Date.now();
this.interval = null;
}
start (f, ms) {
// Stop previous timer (if any)
this.stop();
// Start new timer
this.interval = setInterval( () => {
const now = Date.now();
// Don't execute before given time interval
if (now - this.last < ms)
return;
this.last = now;
f();
}, ms);
}
stop () {
if (this.interval)
clearInterval(this.interval);
this.interval = null;
}
}
const timer = new Timer();
timer.start( () => console.log(Date.now()), 1000);

Timing in JS - multiple setIntervals running at once and starting at the same time?

Let's say I have a function:
myFunc = function(number) {
console.log("Booyah! "+number);
}
And I want it to run on a set interval. Sounds like I should use setInterval, huh!
But what if I want to run multiple intervals of the same function, all starting at the exact same time?
setInterval(function(){
myFunc(1);
}, 500);
setInterval(function(){
myFunc(2);
}, 1000);
setInterval(function(){
myFunc(3);
}, 2000);
So that the first runs exactly twice in the time it takes the second to run once, and the same between the second and third.
How do you make sure that they all start at the same time so that they are in sync?
Good question, but in JS you can't. To have multiple functions in the same program execute at the same time you need multi-threading and some deep timing and thread handling skills. JS is single threaded. setInterval doesn't acutally run the function after the delay, rather after the delay it adds the function to the event stack to be run as soon as the processor can get to it. If the proc is busy with another operation, it will take longer than the delay period to actually run. Multiple intervals/timeouts are all adding calls to the same event stack, so they run in turn as the proc is available.
function Timer(funct, delayMs, times)
{
if(times==undefined)
{
times=-1;
}
if(delayMs==undefined)
{
delayMs=10;
}
this.funct=funct;
var times=times;
var timesCount=0;
var ticks = (delayMs/10)|0;
var count=0;
Timer.instances.push(this);
this.tick = function()
{
if(count>=ticks)
{
this.funct();
count=0;
if(times>-1)
{
timesCount++;
if(timesCount>=times)
{
this.stop();
}
}
}
count++;
};
this.stop=function()
{
var index = Timer.instances.indexOf(this);
Timer.instances.splice(index, 1);
};
}
Timer.instances=[];
Timer.ontick=function()
{
for(var i in Timer.instances)
{
Timer.instances[i].tick();
}
};
window.setInterval(Timer.ontick, 10);
And to use it:
function onTick()
{
window.alert('test');
}
function onTick2()
{
window.alert('test2');
}
var timer = new Timer(onTick, 2000,-1);
var timer = new Timer(onTick2, 16000,-1);
For a finite number of ticks, change the last parameter to a positive integer for number. I used -1 to indicate continuous running.
Ignore anyone who tells you that you can't. You can make it do just about any thing you like!
You can make something like this.
arr = Array();
arr[0] = "hi";
arr[1] = "bye";
setTimer0 = setInterval(function(id){
console.log(arr[id])
},1000,(0));
setTimer1 = setInterval(function(id){
console.log(arr[id]);
},500,(1));
Hope it helps!
JavaScript is single threaded. You can use html5 web worker or try using setTimeout recursively. Create multiple functions following this example:
var interval = setTimeout(appendDateToBody, 5000);
function appendDateToBody() {
document.body.appendChild(
document.createTextNode(new Date() + " "));
interval = setTimeout(appendDateToBody, 5000);
}
Read this article:
http://weblogs.asp.net/bleroy/archive/2009/05/14/setinterval-is-moderately-evil.aspx
You can use multiples of ticks inside functions, in the example below you can run one function every 0.1 sec, and another every 1 sec.
Obviously, the timing will go wrong if functions require longer times than the intervals you set. You might need to experiment with the values to make them work or tolerate the incorrect timing.
Set a variable to handle tick multiples
let tickDivider = -1
Increase the value of tick variable inside the faster function
const fastFunc = ()=> {
tickDivider += 1
console.log('fastFunciton')
}
Use a condition to on running the slower function
const slowFunc = ()=> {
if (!(tickDivider % 10)){
console.log('slowFunction')
}
}
Call both functions in a single one. The order is not important unless you set tickDivider to 0 (of any multiple of 10)
const updateAllFuncs = () => {
fastFunc()
slowFunc()
}
Set the interval to the frequency of the faster function
setInterval(updateAllFuncs, 100)
What I'm doing here is adding a speed attribute to the HTML elements. These speeds are passed as a parameter to setCounter(). I did this mainly to make the code easier to test and play with.
The function setCounter() is invoked inside a loop for every HTML element with class counter. This function sets a new setInterval in every execution.
The intervals seem to be working in sync.
const elements = document.querySelectorAll('.counter')
elements.forEach((el, i) => {
let speed = Number(elements[i].getAttribute('speed'))
setCounter(el, speed, 5000)
})
function setCounter(element, speed, elapse){
let count = 0
setInterval(() => {
count = (count >= elapse) ? elapse : count + speed
if(count === elapse) clearInterval()
element.innerHTML = count
}, 1)
}
Same Speeds
<p class="counter" speed='10'></p>
<p class="counter" speed='10'></p>
Different Speeds
<p class="counter" speed='3'></p>
<p class="counter" speed='5'></p>

Categories

Resources