Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I am trying to automate fetching data from a source and assigning it to a variable that goes up by 1 each loop. Each call to the data source returns a different URL (of a picture of a cat, in this case).
var i;
var urlCount = 7;
for (i=0; i < urlCount; i++) {
this.setState({...this.state, isFetching: true});
var response = await axios.get(USER_SERVICE_URL);
this.image = response.data[0];
url[i] = this.image.url;
console.log([i] + url[i]);
}
What I'm wanting it to do is create a list similar to the following:
url0 = (url here),
url1 = (another url here),
url2 = (yet another url here),
etc...
until the loop meets its condition. It works when I set the code to a manual value, like this:
url0 = this.image.url;
console.log([i] + url0);
But it doesn't work when I try to replace 0 with [i], as in the first example. It seems to be treating [i] as a property of url, instead of appending it to url. How can I make React append a number to a variable so that I can achieve the desired result above?
would not recommend to do this with a for loop, also missing information about the component itself. Also using an object here as an example, but would recommend an array.
// assuming a functional component
// add a state with a object (would rather go with an array)
const [urls, setUrls] = useState({});
// this should be within a function, better a useEffect with
// an async function defined inside and then called within the effect
var i;
var urlCount = 7;
// set fetching state
this.setState({ isFetching: true });
// loop (btw not recommended with async, better go with a map, return promises and then await Promise.all(yourArray))
for (i = 0; i < urlCount; i++) {
var response = await axios.get(USER_SERVICE_URL);
this.setState((oldState) => ({ ...oldState, [`url${i}`]: response.data[0] }));
}
// now you could access
const { url0, url1, url2... } = urls;
This is a suggestion or a tip rather than an answer
The state variable isFetching is used like a flag. You are using the setState method before fetching the data.
Calling this method will re-render the component.
Also it is called in a for loop. So the component will re-render multiple times.
My suggestion is avoid setting state unnecessarily. It will re-render the components and cause unexpected behavior.
Try this code
var i;
var urlCount = 7;
//If you set state inside a for loop and urlCount is a large value
//your component will render many times unnecessarily
this.setState({...this.state, isFetching: true});
for (i=0; i < urlCount; i++) {
var response = await axios.get(USER_SERVICE_URL);
this.image = response.data[0];
url[i] = this.image.url;
console.log([i] + url[i]);
}
Related
Here is a change to a long code before a render that wasted me time to correct many lines in the code:
Before:
const my_model:MyModel = JSON.parse(localStorage.getItem('my_model')!)
const my_positions:MyModel = JSON.parse(localStorage.getItem('my_positions')!)
After:
const waitformyIcm:Boolean = useDetailMyModelQuery(paramid.id).isSuccess;
const myIcm:undefined | MyModel| any = useDetailMyModelQuery(paramid.id).data
const waitforpositions:Boolean = usePatternPositionsPerModelQuery(paramid.icm).isSuccess;
const positions:undefined | PatternPositions[] = usePatternPositionsPerModelQuery(paramid.icm).data
The consequence is that for almost all the next constants I needed to let the program wait until those first lines completed loading. My question is how to cope with such situations because I feel my approach was not as it should:
For example this previous line would already cause crashes:
const harvestAfter:number[] = [myIcm.length_period1, myIcm.length_period2, myIcm.length_period3]
This was solved by:
const harvestAfter:number[] = waitformyIcm && [myIcm.length_period1, myIcm.length_period2, myIcm.length_period3]
Another challenge was to set a default state with data that had to be loaded first. I solved that with:
const [myFilters, setMyFilters] = useState(myfilters);
useEffect(() => setMyFilters(myfilters), [myfilters && waitforpositions]);
How to properly deal with code that need to wait for previous constants to be loaded in REACT?
Using myconst && ......?
Using useEffect?
Using useRef?
Using async await (how to do that for declaring constants)
Other?
Please don't call the same hook multiple times, that is just a waste of user memory and CPU time. (These hooks are doing work internally and you invoke them twice for no good reason.)
Just don't annotate the types here and assign them to a variable. They are already 100% typesafe from the hook.
Then, use skip:
const icmResult = useDetailMyModelQuery(paramid.id);
const patternsResult = usePatternPositionsPerModelQuery(paramid.icm, { skip: !icmResult.isSuccess });
As for the variable assignment, you could also use destructuring although I don't really see the point:
const { isSuccess: waitformyIcm, data: myIcm } = useDetailMyModelQuery(paramid.id);
As for a default state, there are many approaches. If it is an object you define inline, it would be a new reference on every render, so you best useMemo to get around that:
const filtersWithDefaults = useMemo(() => filters ?? defaultFilters, [filters])
I have the following function
compareProducts = (empresa) => {
console.log(empresa.listaProductos)
let headerSetIn = false;
for (let i in empresa.listaProductos) {
//case1 : lookup for some data in an array, if found, setState and exit the whole function
if (pFichaInternacional && pFichaInternacional.length > 0) {
console.log("caso1")
let product: any = pFichaInternacional;
let nombreEmpresaApi = empresa.listaProductos[i].nombre
let productoFiltrado = product.filter(i => i.referencia == nombreEmpresaApi)
productoFiltrado = productoFiltrado[0]
if (productoFiltrado) {
headerSetIn = true
this.setState({
headerCardText: productoFiltrado.descripcion.toString(),
headerButtonText: productoFiltrado.label.toString(),
})
break;
}
}
}
//case 2: case1 didnt found the data, so we setup some predefined data.
if (!headerSetIn && pFichaInternacional.length > 0) {
let product: any = pFichaInternacional;
this.setState({
headerCardText: product[0].descripcion.toString(),
headerButtonText: product[0].label.toString()
})
}
}
Im receiving a
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I have also tried using a setstate , instead of a local variable to set the headerSetIn parameter. But if I do it, I think js doesnt have time to evaluate the change, and both are executed, instead of only 1
Ive tried to use () , => after the first state, but it doesnt make sense in my flow
As far as I can understand is that you are calling setState in a for loop, which is not a good thing. Every time a certain condition is met setState is called, so you are constantly setting the state and rerendering smashing your performance and causing this.
I would suggest using a variable and get the needed data in the for loop then after you have excited the for loop simply use setState to set the state after all the data has been looped trough.
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
The community reviewed whether to reopen this question 3 months ago and left it closed:
Duplicate This question has been answered, is not unique, and doesn’t differentiate itself from another question.
I am running an event loop of the following form:
var i;
var j = 10;
for (i = 0; i < j; i++) {
asynchronousProcess(callbackFunction() {
alert(i);
});
}
I am trying to display a series of alerts showing the numbers 0 through 10. The problem is that by the time the callback function is triggered, the loop has already gone through a few iterations and it displays a higher value of i. Any recommendations on how to fix this?
The for loop runs immediately to completion while all your asynchronous operations are started. When they complete some time in the future and call their callbacks, the value of your loop index variable i will be at its last value for all the callbacks.
This is because the for loop does not wait for an asynchronous operation to complete before continuing on to the next iteration of the loop and because the async callbacks are called some time in the future. Thus, the loop completes its iterations and THEN the callbacks get called when those async operations finish. As such, the loop index is "done" and sitting at its final value for all the callbacks.
To work around this, you have to uniquely save the loop index separately for each callback. In Javascript, the way to do that is to capture it in a function closure. That can either be done be creating an inline function closure specifically for this purpose (first example shown below) or you can create an external function that you pass the index to and let it maintain the index uniquely for you (second example shown below).
As of 2016, if you have a fully up-to-spec ES6 implementation of Javascript, you can also use let to define the for loop variable and it will be uniquely defined for each iteration of the for loop (third implementation below). But, note this is a late implementation feature in ES6 implementations so you have to make sure your execution environment supports that option.
Use .forEach() to iterate since it creates its own function closure
someArray.forEach(function(item, i) {
asynchronousProcess(function(item) {
console.log(i);
});
});
Create Your Own Function Closure Using an IIFE
var j = 10;
for (var i = 0; i < j; i++) {
(function(cntr) {
// here the value of i was passed into as the argument cntr
// and will be captured in this function closure so each
// iteration of the loop can have it's own value
asynchronousProcess(function() {
console.log(cntr);
});
})(i);
}
Create or Modify External Function and Pass it the Variable
If you can modify the asynchronousProcess() function, then you could just pass the value in there and have the asynchronousProcess() function the cntr back to the callback like this:
var j = 10;
for (var i = 0; i < j; i++) {
asynchronousProcess(i, function(cntr) {
console.log(cntr);
});
}
Use ES6 let
If you have a Javascript execution environment that fully supports ES6, you can use let in your for loop like this:
const j = 10;
for (let i = 0; i < j; i++) {
asynchronousProcess(function() {
console.log(i);
});
}
let declared in a for loop declaration like this will create a unique value of i for each invocation of the loop (which is what you want).
Serializing with promises and async/await
If your async function returns a promise, and you want to serialize your async operations to run one after another instead of in parallel and you're running in a modern environment that supports async and await, then you have more options.
async function someFunction() {
const j = 10;
for (let i = 0; i < j; i++) {
// wait for the promise to resolve before advancing the for loop
await asynchronousProcess();
console.log(i);
}
}
This will make sure that only one call to asynchronousProcess() is in flight at a time and the for loop won't even advance until each one is done. This is different than the previous schemes that all ran your asynchronous operations in parallel so it depends entirely upon which design you want. Note: await works with a promise so your function has to return a promise that is resolved/rejected when the asynchronous operation is complete. Also, note that in order to use await, the containing function must be declared async.
Run asynchronous operations in parallel and use Promise.all() to collect results in order
function someFunction() {
let promises = [];
for (let i = 0; i < 10; i++) {
promises.push(asynchonousProcessThatReturnsPromise());
}
return Promise.all(promises);
}
someFunction().then(results => {
// array of results in order here
console.log(results);
}).catch(err => {
console.log(err);
});
async await is here
(ES7), so you can do this kind of things very easily now.
var i;
var j = 10;
for (i = 0; i < j; i++) {
await asycronouseProcess();
alert(i);
}
Remember, this works only if asycronouseProcess is returning a Promise
If asycronouseProcess is not in your control then you can make it return a Promise by yourself like this
function asyncProcess() {
return new Promise((resolve, reject) => {
asycronouseProcess(()=>{
resolve();
})
})
}
Then replace this line await asycronouseProcess(); by await asyncProcess();
Understanding Promises before even looking into async await is must
(Also read about support for async await)
Any recommendation on how to fix this?
Several. You can use bind:
for (i = 0; i < j; i++) {
asycronouseProcess(function (i) {
alert(i);
}.bind(null, i));
}
Or, if your browser supports let (it will be in the next ECMAScript version, however Firefox already supports it since a while) you could have:
for (i = 0; i < j; i++) {
let k = i;
asycronouseProcess(function() {
alert(k);
});
}
Or, you could do the job of bind manually (in case the browser doesn't support it, but I would say you can implement a shim in that case, it should be in the link above):
for (i = 0; i < j; i++) {
asycronouseProcess(function(i) {
return function () {
alert(i)
}
}(i));
}
I usually prefer let when I can use it (e.g. for Firefox add-on); otherwise bind or a custom currying function (that doesn't need a context object).
var i = 0;
var length = 10;
function for1() {
console.log(i);
for2();
}
function for2() {
if (i == length) {
return false;
}
setTimeout(function() {
i++;
for1();
}, 500);
}
for1();
Here is a sample functional approach to what is expected here.
ES2017: You can wrap the async code inside a function(say XHRPost) returning a promise( Async code inside the promise).
Then call the function(XHRPost) inside the for loop but with the magical Await keyword. :)
let http = new XMLHttpRequest();
let url = 'http://sumersin/forum.social.json';
function XHRpost(i) {
return new Promise(function(resolve) {
let params = 'id=nobot&%3Aoperation=social%3AcreateForumPost&subject=Demo' + i + '&message=Here%20is%20the%20Demo&_charset_=UTF-8';
http.open('POST', url, true);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.onreadystatechange = function() {
console.log("Done " + i + "<<<<>>>>>" + http.readyState);
if(http.readyState == 4){
console.log('SUCCESS :',i);
resolve();
}
}
http.send(params);
});
}
(async () => {
for (let i = 1; i < 5; i++) {
await XHRpost(i);
}
})();
JavaScript code runs on a single thread, so you cannot principally block to wait for the first loop iteration to complete before beginning the next without seriously impacting page usability.
The solution depends on what you really need. If the example is close to exactly what you need, #Simon's suggestion to pass i to your async process is a good one.
Background
Developing a Chrome extension (latest Chrome running on Mac OS Sierra) and I can't work out how to loop over an array which is also dynamically built at runtime.
Forgive me if I am missing something really obvious, but I cannot for the life of me work out why this for..of loop is not being entered.
I've also tried a for..in and the good old classic for loop structure i.e. for (let i = 0; i < array.length; i++) - no matter what style of loop this block is never entered, despite my array at runtime reportedly having a single item in it.
Problem Code and Statement
This code gets all files inside a directory and slices off the last 3 chars (to remove .js):
const getDirectoryContents = (path) => {
let fileNames = []
chrome.runtime.getPackageDirectoryEntry( (directoryEntry) => {
directoryEntry.getDirectory(path, {}, (subDirectoryEntry) => {
const subDirectoryReader = subDirectoryEntry.createReader()
subDirectoryReader.readEntries( (entries) => {
for (const entry of entries) {
fileNames.push(entry.name.slice(0, -3))
}
})
})
})
return fileNames
}
From inside the chrome.runtime.onStartup() callback function we want to add some context menus, which we do like so:
const addContextMenus = () => {
console.log(getDirectoryContents('commands'))
for (const command of getDirectoryContents('commands')) {
const properties = {
id: command,
title: command,
contexts: ['editable']
}
chrome.contextMenus.create(properties)
console.log(`Created context menu ${properties.title}`)
}
console.log('End addContextMenus')
}
Now, during runtime, the above code will output this inside the background page console:
However as we can see (due to the lack of the console logging "Created context menu ..." - the loop is never entered, despite the array having a length of 1.
I've found nothing online inside the Chrome developer docs that indicated that getDirectoryContents is asynchronous -- which would be one possible explanation -- but just to be sure I even tried adding a callback param to the getDirectoryContents function to ensure we are looping after the array has been populated.
EDIT: after closer inspection of the original function, it's clear that the array is in fact being returned before is has a chance to be populated by the directory reader. Answer below.
Same result!
Any help would be much appreciated. Thanks for your time.
How embarrassing! Passing in a callback function and executing it at the right time solved it. Comments were all correct - typical async issue - thanks for the support.
The problem was on line 15 of the original function: I was returning the array before it had a chance to be populated.
Working function:
const getDirectoryContents = (path, callback) => {
chrome.runtime.getPackageDirectoryEntry( (directoryEntry) => {
directoryEntry.getDirectory(path, {}, (subDirectoryEntry) => {
const subDirectoryReader = subDirectoryEntry.createReader()
let fileNames = []
subDirectoryReader.readEntries( (entries) => {
for (const entry of entries) {
fileNames.push(entry.name.slice(0, -3))
}
callback(fileNames)
})
})
})
}
In one of my controller functions in my angular.js application, I'm trying to use Google's RSS Feed API to fetch feed entries to populate in my scope.items model.
It's behaving quite strangely, with the console output in the innermost part always being '2' but while the console output in the outermost loop being '0' and '1' (which is correct since that is the length of my items array).
I'm thinking it could have something to do with the Google API thing being an async request and that it hasn't finished before I try to manipulate it (?). Still doesn't make sense that my iterator variable would become '2' though!
Code:
function ItemListCtrl($scope, Item) {
$scope.items = Item.query({}, function() { // using a JSON service
for (var i = 0; i < $scope.items.length; i++) { // the length is 2 here.
$scope.items[i].entries = [];
console.log(i); // gives '0' and '1' as output in the iterations.
var feed = new google.feeds.Feed($scope.items[i].feedUrl);
feed.load(function(result) {
console.log(i); // gives '2' as output in both the iterations.
if (!result.error) {
for (var j = 0; j < result.feed.entries.length; j++) {
$scope.items[i].entries[j] = result.feed.entries[j];
}
}
});
}
});
}
The callback function is executed asynchonously, after the loop over the items has ended. And at the end of the loop, i is equal to 2, since there are 2 items in your items array.
See Javascript infamous Loop issue? for another example of the same behavior, and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures#Creating_closures_in_loops.3A_A_common_mistake for an more in-depth explanation.