Why this setInterval is executing multiple times? - javascript

I have the below code in a vue application
mounted: function () {
this.timer = setInterval(async () => {
if (this.progress >= 1) {
this.progress = 1
clearInterval(this.timer)
}
console.log('update')
const id = this.$route.params.id
const progOut = await this.api.get(`/api/mu/job/${id}/status`)
const response = progOut.data
this.progress = response.data.progress / 100
this.state = response.data.status
}, 7000)
},
I was expecting it to execute the get request every 7 seconds but it is executing the call every 500ms approx
I read other answers and so far I think this is the proper way but the code is executing too many requests
What is the proper way to call a function from within the setInterval to make it actually wait the timeout?
Edit: This was my final code in case someone goes through the same
methods: {
redirect (page) {
if (page === 'FINISHED') {
this.$router.push({
name: 'viewReport',
params: { id: 4 }
})
} else {
this.$router.push({
name: 'errorOnReport',
params: { id: 13 }
})
}
}
},
watch: {
state: async function (newVal, old) {
console.log('old ' + old + ' newVal ' + newVal)
if (newVal === 'FAILED' || newVal === 'FINISHED') {
this.redirect(newVal)
}
}
},
data () {
return {
state: null,
timer: null,
progress: 0.0,
progressStr: '0%'
}
},
mounted () {
const update = async () => {
if (this.progress >= 1) {
this.progress = 1
}
console.log('update ' + new Date())
const id = this.$route.params.id
const progOut = await this.api.get(`/api/mu/job/${id}/status`)
const response = progOut.data
this.state = response.data.status
this.progress = response.data.progress / 100
this.progressStr = response.data.progress + '%'
}
update()
this.timer = setInterval(update, 10000)
},
beforeUnmount () {
clearInterval(this.timer)
}

A better design is to wrap setTimeout with a promise, and do the polling in an async method that loops...
mounted: function() {
this.continuePolling = true; // suggestion: we have to stop sometime. consider adding continuePolling to data
this.poll();
},
unmounted: function() { // almost the latest possible stop
this.continuePolling = false;
},
methods:
async poll(interval) {
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
while(this.continuePolling) {
await this.updateProgress();
await delay(7000);
}
},
async updateProgress() {
const id = this.$route.params.id
const progOut = await this.api.get(`/api/mu/job/${id}/status`)
const result = progOut.data.data;
this.progress = result.progress / 100
this.state = result.status
}

Related

Check two async boolean variables and call a method if both are satisfied

How could I improve this method of rendering only when both variables are met as true, to allow the renderFilters() method to be called:
These two variables are filled asynchronously through 2 API methods:
//getManager()
this.isLoadingManager = true;
//getPdiPOrganization()
this.isLoadingPdiOrganization = true;
promiseRender() {
let interval = setInterval(() => {
if (this.isLoadingManager && this.isLoadingPdiOrganization) {
clearInterval(interval);
this.renderFilters();
} else {
setTimeout(() => {
clearInterval(interval);
this.renderFilters();
}, 5000)
}
}, 500);
}
The problem is that it's very slow... it's calling long after the APIs are called...
Maybe some feature of angular itself, if anyone has a better solution...
const observable = forkJoin({
loading1:this.isLoadingManager,
loading2:this.isLoadingPdiOrganization
});
observable.subscribe({
next: (results) => {
const obs1Val = results[0];
const obs2Val = results[1];
if (obs1Val && obs2Val) {
this.renderFilters();
}
}
})
Or:
const myObservable = Observable.of(this.isLoadingManager && this.isLoadingPdiOrganization);
const myObserver = {
next: (result: Boolean) => this.renderFilters(),
};
myObserver.next(true);
myObservable.subscribe(myObserver);
Adding the methods:
getManager() {
if (this.fromAdminPage && localStorage.getItem('_receivers_pdi')) {
this.meetingService.getIsManager()
.subscribe(res => {
this.showPdiToastNotification = res;
this.isLoadingManager = true;
});
}
}
getPdiPOrganization() {
const url = this.publicEndpoint ? 'current/organization/pdi/configuration' : 'api/current/organization/pdi/configuration';
const requestOptions = {
params: new CustomHttpParams({ isPublicTokenUrl: this.publicEndpoint })
};
this.http.get<any>(url, requestOptions).subscribe(resp => {
this.isLoadingPdiOrganization = true;
this.pdiOrgConfig = resp || {};
this.updatePdiReferenceType(this.pdiOrgConfig);
});
}
You can use forkjoin to subscribe to two observables at the same time. I would stick with using RxJs operators for cases like these. You can read more about forkJoin here.
forkJoin([obs1, obs2]).subscribe({
next: (results) => {
const obs1Val = results[0];
const obs2Val = results[1];
if (obs1Val && obs2Val) {
this.renderFilters();
}
}
});

State Variables become null When UseEffect hook is used

I'm trying to make new requests whenever user reach the bottom with the following;
useEffect(() => {
const scrolling_function = () => {
if((window.innerHeight + window.scrollY) >= document.body.offsetHeight-10){
window.removeEventListener('scroll',scrolling_function)
getMoviesFromApi()
}
}
window.addEventListener('scroll', scrolling_function);
}, [])
But the state objects that I defined, such as:
const[selectedGenres, setSelectedGenres] = useState(new Set())
All becomes undefined in the inside of my useEffect hook, and thus my getMoviesFromApi() method does not work properly.
My question is, is that expected behavior? If so, how could I overcome it?
The getmoviesfromapi method:
const getMoviesFromApi = async () => {
setLoading(true)
let init, end;
if(initialYear>endYear) {
init = endYear
end = initialYear
} else {
init = initialYear
end = endYear
}
let res =await fetchMovies(init, end)
setLoading(false)
setMovies(res.results)
}
The fetchMovies method:
const fetchMovies = async (startYear, endYear) => {
let res;
res = [];
let genreQuery = "";
let serviceQuery = "";
for (let it = selectedGenres.values(), val= null; val=it.next().value; ) {
genreQuery += "&genre=" + val;
}
for (let it = selectedServices.values(), val= null; val=it.next().value; ) {
serviceQuery += "&service=" + val;
}
let countryQuery = "?country="+country;
let yearQuery = "&year_min="+ startYear +"&year_max=" + endYear;
let typeQuery = "&type=" + (isMovie ? "movie" : "series");
let url = api_url + countryQuery + serviceQuery + typeQuery +"&order_by=year" + yearQuery
+ genreQuery + "&page=1&desc=true&language=en&output_language=en"
await fetch(url, {
"method": "GET",
}).then(response => {
res= response.json()
})
.catch(err => {
console.error(err);
});
return res;
};
You should add the necessary state objects and functions to the dependencies, and call the functions with useCallBack. You can be sure that you're making it right by installing eslint-plugin-react-hooks as a dev dependency. You'd also need a cleanUp function for your effect hook.
useEffect( () =>{
const scrolling_function = async () => {
if((window.innerHeight + window.scrollY) >= document.body.offsetHeight-10){
await getMoviesFromApi(false);
}
}
window.addEventListener('scroll', scrolling_function);
return function cleanUp() { //don't forget to clean up
window.removeEventListener('scroll',scrolling_function);
}
}, [getMoviesFromApi]); //add method as dependency!
//...
const getMoviesFromApi = useCallback () => { //useCallBack is needed here!
...
}

Rendered fewer hooks than expected. This may be caused by an accidental early return statement

I'm getting this error when triggering a setState inside of a custom React hook. I'm not sure of how to fix it, can anyone show me what I'm doing wrong. It is getting the error when it hits handleSetReportState() line. How should I be setting the report state from inside the hook?
custom useinterval poll hook
export function usePoll(callback: IntervalFunction, delay: number) {
const savedCallback = useRef<IntervalFunction | null>()
useEffect(() => {
savedCallback.current = callback
}, [callback])
useEffect(() => {
function tick() {
if (savedCallback.current !== null) {
savedCallback.current()
}
}
const id = setInterval(tick, delay)
return () => clearInterval(id)
}, [delay])
}
React FC
const BankLink: React.FC = ({ report: _report }) => {
const [report, setReport] = React.useState(_report)
if ([...Statues].includes(report.status)) {
usePoll(async () => {
const initialStatus = _report.status
const { result } = await apiPost(`/links/search` });
const currentReport = result.results.filter((item: { id: string; }) => item.id === _report.id)
if (currentReport[0].status !== initialStatus) {
handleSetReportState(currentReport[0])
console.log('status changed')
} else {
console.log('status unchanged')
}
}, 5000)
}
... rest
This is because you put usePoll in if condition, see https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
You can put the condition into the callback
usePoll(async () => {
if ([...Statues].includes(report.status)) {
const initialStatus = _report.status
const { result } = await apiPost(`/links/search` });
const currentReport = result.results.filter((item: { id: string; }) => item.id === _report.id)
if (currentReport[0].status !== initialStatus) {
handleSetReportState(currentReport[0])
console.log('status changed')
} else {
console.log('status unchanged')
}
}
}, 5000)
And if the delay will affect report.status, use ref to store report.status and read from ref value in the callback.

Increase and Decrease the values

I have a problem. The values from the data array should be increased or decreased every 5 seconds with 0.0001 by following the rule: values should go up for the first minute, then down for the next minute, and so on -
const data = [
{ AUD: "1.5876" }, { BGN: "1.9558" }, { GBP: "0.8527" }, { USD: "1.1820" },
{ UYU: "51.9732" }, { UZS: "12570.5509" }, { VEF: "252746931045.8590" },
{ VND: "27195.9489" }, { VUV: "130.1601" }, { WST: "3.0161" }, { XAG: "0.0449" },
{ XAU: "0.0006" }, { XCD: "3.1944" }, { XDR: "0.8306" },
];
This is my code, but i got an infinity loop.
const [currencies, setCurrencies] = useState([]);
let initialTime = true;
useEffect(() => {
let init = setTimeout(() => {
initialTime = false;
console.log('changing the time ' + initialTime);
}, 5000);
return () => clearTimeout(init)
}, []);
function increase() {
data.forEach(e => {
let value = Number(Object.values(e)[0]);
const key = Object.keys(e)[0];
setCurrencies({ [key]: value += 0.0001 });
})
console.log(currencies);
}
let interval = setInterval(() => {
if (initialTime) {
increase()
} else {
return () => clearInterval(interval);
}
}, 1000);
So keep track of the time and determine how long it has been and when it hits the length of time, flip it.
let updateTime = Date.now();
const endTime = updateTime + 20000;
let dir = 1;
let value = 1.2357;
const flipTime = 5000;
const timer = setInterval(function () {
if(Date.now() - updateTime >= flipTime) {
updateTime = Date.now();
dir *= -1;
}
value += dir * .0001;
console.log(value.toFixed(4));
// If over end time, stop looping
if (Date.now() >= endTime) {
window.clearTimeout(timer);
}
}, 1000);
Side note, setInterval is not 100% accurate so you will see the numbers float around as time runs on.

How cancel Timeout inside object

I have the following code:
const timers = []
const timer1 = setTimeout(() => {
console.log('Starting timer2')
const timer2 = setTimeout(() => {
console.log('Its works')
}, 10000)
timers.push({key:2, id:timer2})
}, 10000);
timers.push({key:1, id:timer1})
function remove(key) {
for (i = 0; i > timers.length; i++) {
if (timers[i].key === key) {
timers = timers.slice(i, 1)
clearTimeout(timers[i].id)
}
}
}
When I call the remove(key) function the code is not removing the timers as expected
const timers = []
const timer1 = setTimeout(() => {
console.log('Starting timer2')
const timer2 = setTimeout(() => {
console.log('Its works')
}, 10000)
timers.push({key:2, id:timer2})
}, 10000);
timers.push({key:1, id:timer1})
function remove(key) {
const timer = timers.find(f => f.key === key);
if (timer) {
clearTimeout(timer.id);
}
}

Categories