using setInterval and setimeout to make 3 components load after each other - javascript

I am making a simple react app, it just loads one component when it reached 100% then it loads the second one and after its 100% it loads the third one.
this is how it works, I have a start button, when you click it, it starts the first function which loads the first component:
<Button
variant="outlined"
type="submit"
onClick={() => {
setLoading(true);
start();
}}
>
Start
</Button>
and here are the loading components they are all the same expect the state is different:
const load1 = () => {
const timer = setInterval(() => {
setProgress1((prevProgress) =>
prevProgress >= 100 ? 100 : prevProgress + 10
);
}, 800);
return () => {
clearInterval(timer);
};
};
const load2 = () => {
const timer = setInterval(() => {
setProgress2((prevProgress) =>
prevProgress >= 100 ? 100 : prevProgress + 10
);
}, 800);
return () => {
clearInterval(timer);
};
};
const load3 = () => {
const timer = setInterval(() => {
setProgress3((prevProgress) =>
prevProgress >= 100 ? 100 : prevProgress + 10
);
}, 800);
return () => {
clearInterval(timer);
};
};
and my start looks like this:
const analyseDNA = () => {
setTimeout(load1, 2000);
setTimeout(setLoading2(true), 2000);
setTimeout(load2, 4000);
setTimeout(setLoading3(true), 4000);
setTimeout(load3, 6000);
};
what is supposed to happen is to load component 1 and then component 2 and then component 3 after each other. then you can press start again and it does the same.
however, with this code, component 1 and component 2 are loaded together, and then after 6 seconds component 3, when you press start again it only loads component 1, and component 3 is already loaded and nothing happens with component 2. when I open console I can see that it is printing 1, 2, 3, and it seems like the interval is never ends and it keeps going , how can I fix this?

I've made you a code sandbox. Could be prettier but it should get you on the right path. The trick would be to pass in a callback function to the load function. When the loader reaches 100 from the interval, invoke that callback which calls the next loader.
Personally, I think promises are more suitable. You would do something along the lines of creating a set interval inside a promise, then resolve the promise when the value reaches 100. This way you could make your analyseDNA function more like this:
const analyseDNA = async () => {
await load1()
await load2()
await load3()
};
Here is a sandbox for a "promise" approach. Of course, you might need to adapt if you want to show progress.

Related

JavaScript clear timeout not working with React useEffect return function

For some reason, I need to call checkProgess function for the first 2 minutes with 10 seconds of delays.
So I did it like this
const [timers, setTimers] = useState<ReturnType<typeof setTimeout>[]>([]);
useEffect(()=>{
for (let index = 0; index < 12; index++) {
const seconds = (index+1)*10000;
let timer = setTimeout(() => {
checkProgress(id, setFieldValue, contractType, fileOCRStatus);
}, seconds);
console.log("timer",timer)
setTimers((prev) => {
prev.push(timer)
return prev
});
}
},[])
within these 12 tries, this component will unmount if the progress checks succeed. In that time I do want to clear all the remaining timeout calls. I did it like this in the useEffect return function.
return () => {
console.log("Return function called",timers)
timers.forEach((timer) => clearTimeout(timer));
};
This part is executed successfully but the cleaning thing seems not working. I CAN SEE THE API CALLS RUNNING AFTER THE COMPONENT IS UNMOUNTED.
what went wrong here?
In console.log("Return function called", timers) timer ids also print correctly.
You don't actually need to store these in a state since this useEffect is only running once (and even if it wasn't, since you're cleaning up the timers in the returned function, you don't need to keep the value across renders).
// delete the useState stuff
useEffect(() => {
const timers = [];
for (let index = 0; index < 12; index++) {
const seconds = (index+1)*10000;
const timer = setTimeout(() => {
checkProgress(id, setFieldValue, contractType, fileOCRStatus);
}, seconds);
console.log("timer",timer)
timers.push(timer);
}
return () => {
timers.forEach((timer) => clearTimeout(timer));
}
}, []);

Call an API every 2 seconds for a period of one minute using react hooks

I am trying to make an api call (API1) every 2 seconds for a period of 1 minute. During this minute I need to exit the interval if condition is met and do another API call (API2). The problem that I am facing is if the API1 get into a pending state for more than 2 seconds, the second call of API1 will start and again if in pending state for 2 seconds the third call will happen ending up with quite few calls for same api all under pending state …. How can I stop the subsequent calls until the previous call is resolved?
useEffect(() => {
if (triggerTheInterval) {
startInterval()
return () => clearInterval(id)
}
}, [triggerTheInterval])
onClick = () => {
makefirstcallforapi1();
}
//this is only to get the first call at first second - before the 2 seconds interval starts
const makefirstcallforapi1 = () => {
setTimer(new Date().getTime());
fetch(url).then(res => {
if (conditionmet) {
//do the API2 call
} else {
setTriggerTheInterval(true)
}
})
startInterval = () => {
id = setInterval(() => {
fetch(url).then(res => {
if ((new Date().getTime() - startTime > 60000 || condtionmet) {
//do something then exit the interval
}
})
}, 2000)
}

Multiple apis trigger without using settimeout

I have calling dataUpdate function every 4 second until records length to 0.
Inside the dataUpdate this.callAPI(); is trigger api call.
I set manual timeout for 4 seconds to call the function to callapi.
Instead of timeout ,how to call the api immediately after previous api completed.
dataUpdate =()=> {
var arrayList = this.arrayList;
if(arrayList.length > 0)
{
var inputData = {
...inputData,
data:{
...inputData.data,first:'',second:''
}
};
var first = arrayList[0].first;
var second = arrayList[0].second;
inputData.data.first = first;
inputData.data.second = second;
this.setState({ inputData:inputData });
this.callAPI();
arrayList.shift();
this.arrayList = arrayList;
if(arrayList.length !== 0){
setTimeout(() => {
this.dataUpdate();
}, 4000);
}
if(arrayList.length === 0){
setTimeout(() => {
this.props.callMessage(this.totalCount);
}, 1000);
}
}
}
Your problem is in having state. Make callapi function stateless and pass all needed parameters so you can call it any amount of times and do not rely on previous call or state

Set Timeout running 4 times each minute

for some reason a function I would like to execute every minute is running 4 times every minute. I would like the behavior to only fire a single time. I am unsure of why it is firing multiple times. the code is as follows:
const checkToken = () => {
console.log('im running')
const token = localStorage.FBIdToken
if (token && token !== 'Bearer undefined') {
const decodedToken = jwtDecode(token)
if (decodedToken.exp * 1000 < Date.now()) {
localStorage.removeItem('FBIdToken')
window.location.reload()
}
}
setTimeout(checkToken, 60 * 1000)
}
checkToken()
you are using this script in react, so make sure that this function/method is not triggering with component re-rendering. if you are using stateful component then move this function to componentDidMount to stop the multiple calls to this method. and if you are using stateless component then use hooks to avoid this issue
you can also use the clearInterval to avoid this issue
const timeInterVal = null;
const checkTokenFunc = () => {
if (token && token !== 'Bearer undefined') {
const decodedToken = jwtDecode(token)
if (decodedToken.exp * 1000 < Date.now()) {
localStorage.removeItem('FBIdToken')
window.location.reload()
}
}
}
const checkToken = () => {
if(timeInterval!==null){
clearTimeout(timeInterval);
}
timeInterval = setTimeout(() => {
checkTokenFunc();
checkToken();
}, 60 * 1000)
}
checkToken();
You could use setInterval() instead of setTimeout(). setInterval() you can specify a period of time and it will keep running based on the time interval you set...Pass setInterval() a function to execute and a time interval in milliseconds. The below will execute every 5 seconds.
Example:
setInterval(function(){ alert("Hello"); }, 5000);

How can I unsubscribe or cancel the filtering of a large array that is an RxJS observable?

My understanding is that an entire array is pushed to a subscriber, unlike say an interval observer that can be unsubscribed/cancelled.
For example the following cancellation works...
// emit a value every second for approx 10 seconds
let obs = Rx.Observable.interval(1000)
.take(10)
let sub = obs.subscribe(console.log);
// but cancel after approx 4 seconds
setTimeout(() => {
console.log('cancelling');
sub.unsubscribe()
}, 4000);
<script src="https://unpkg.com/rxjs#5.5.10/bundles/Rx.min.js"></script>
However, replacing the interval with an array doesn't.
// emit a range
let largeArray = [...Array(9999).keys()];
let obs = Rx.Observable.from(largeArray)
let sub = obs.subscribe(console.log);
// but cancel after approx 1ms
setTimeout(() => {
console.log('cancelling');
sub.unsubscribe()
}, 1);
// ... doesn't cancel
<script src="https://unpkg.com/rxjs#5.5.10/bundles/Rx.min.js"></script>
Does each element need to be made asynchronous somehow, for example by wrapping it in setTimeout(..., 0)? Perhaps I've been staring at this problem too long and I'm totally off course in thinking that the processing of an array can be cancelled?
When using from(...) on an array all of the values will be emitted synchronously which doesn't allow any execution time to be granted to the setTimeout that you are using to unsubscribe. Infact, it finishes emitting before the line for the setTimeout is even reached. To allow the emits to not hog the thread you could use the async scheduler (from(..., Rx.Scheduler.async)) which will schedule work using setInterval.
Here are the docs: https://github.com/ReactiveX/rxjs/blob/master/doc/scheduler.md#scheduler-types
Here is a running example. I had to up the timeout to 100 to allow more room to breath. This will slow down your execution of-course. I don't know the reason that you are attempting this. We could probably provide some better advice if you could share the exact use-case.
// emit a range
let largeArray = [...Array(9999).keys()];
let obs = Rx.Observable.from(largeArray, Rx.Scheduler.async);
let sub = obs.subscribe(console.log);
// but cancel after approx 1ms
setTimeout(() => {
console.log('cancelling');
sub.unsubscribe()
}, 100);
// ... doesn't cancel
<script src="https://unpkg.com/rxjs#5.5.10/bundles/Rx.min.js"></script>
I've marked #bygrace's answer correct. Much appreciated! As mentioned in the comment to his answer, I'm posting a custom implementation of an observable that does support such cancellation for interest ...
const observable = stream => {
let timerID;
return {
subscribe: observer => {
timerID = setInterval(() => {
if (stream.length === 0) {
observer.complete();
clearInterval(timerID);
timerID = undefined;
}
else {
observer.next(stream.shift());
}
}, 0);
return {
unsubscribe: () => {
if (timerID) {
clearInterval(timerID);
timerID = undefined;
observer.cancelled();
}
}
}
}
}
}
// will count to 9999 in the console ...
let largeArray = [...Array(9999).keys()];
let obs = observable(largeArray);
let sub = obs.subscribe({
next: a => console.log(a),
cancelled: () => console.log('cancelled')
});
// except I cancel it here
setTimeout(sub.unsubscribe, 200);

Categories

Resources