Cannot update state in reactJS - reactjs

console.log(holder);
console.log(index);
console.log(holder);
setWholeData(wholeData.filter((el, i) => i !== index));
console.log(holder);
For the above code, I want to remove one element with index = 'index' in wholeData which is a state setup using useState and initialize with []. In my test case, before this part of code, wholedata should have two elements and this part of code should remove one of its elements. I assign the updated/New array to holder and want to assign it to wholedata using 'setWholeData()".
But the problem is that if I do
setWholeData(holder). Then the holder will contain two elements, which means that the filter function did not work somehow. If I do
setWholeData(wholeData.filter((el, i) => i !== index));
The value of holder is what I expected, but 'wholedata' is not updated correctly still. The element is not removed. Seems like the filter in the
setWholeData(...) is not working.
If anyone could give a little hand, it would be so appreciated. Sorry for the confusing description, if any clarification is needed, please feel free to message.

Filter returns an array where the result of the callback is 'true'.
If you want to remove an element by its index you can use a combination of findIndex and splice to find the index of the element you want to remove and splice to execute the removal of that element.

Related

How do I remove an item from a nested array in Firestore?

I want to remove the type 'A' from the capital. How do I do it? Any code example will be appreciated. I am working on a react project.
As far as I can tell there is no nested array in the document you shared. In that case you can use the arrayRemove operator to remove a unique item from the array:
const cityRef = doc(db, "cities", "capital");
await updateDoc(cityRef, {
region: arrayRemove({ type: "A" })
});
A few things to note here:
You can to pass the entire array item to the arrayRemove operator, as it only removes array items that exactly and completely match the value you pass.
The arrayRemove operations removes all items that match. So if you have multiple { type: "A" } items in the array, all will be removed.
This operation can only work on an array field at a known path, it cannot work on an array that is nested under another array.
If your use-case can't satisfy any of the requirements above, the way to remove the item would be to:
Load the document and get the array from it.
Update the array in your application code.
Write the entire top-level array back to the database.
you have to get the doc and clone properties into temporary object
you modify your temp object (remove item form region array)
update original doc with temp object
a cleanest way is to do that throught a firestore/transaction
a small example to have a small approach
const doc = await cityRef('cities/capital').get()
const temp = JSON.parse(JSON.stringify(doc.data()))
temp.region = temp.region.filter(item => item.type !== 'A')
await cityRef('cities/capital').update(temp)

Is there a way to call nested array in Vue?

I am really new to Vue JS. I was trying to print my nested object using console.log but it throws me an undeifned error.
Array Image
View Code
<b-button variant="primary" v-on:click="dontknow();">Print</b-button>
Script
methods:{
dontknow(){
console.log(this.allPlayerList.booker_id);
},
}
It displays me undefined when I use console.log(this.allPlayerList.booker_id). Can anyone please let me know what am I doing wrong? I want to get all the booker_id from allPlayerList.
allPlayerList obviously is an array of objects, which doesn't have itself a booker_id property, but contains objects which have it.
To print all the booker_id you need to loop over the array, and print it for every object, there are multiple ways of doing this, some of the common ways are:
this.allPlayerList.forEach(player => {
console.log(player.booker_id);
});
Another way would be
console.log(this.allPlayerList.map(player => return player.booker_id));
The fist way will print every booker_id separately, while the second will print and array of all the booker_id items.

Check if array of objects contains key equals to string

This is angularjs app.
In a view I have access to an array of objects:
var arrayOfObjects = [{"name":"blue"},{"name":"red"}];
I have then a div that should display only if the arrayOfObjects contains an entry with
name=='red'
I had a look at "contains" but seems to work only on arrays of elements, not of objects.
Is it possibile to do this directly in the view without needing to code this in the controller?
Since the array does not understand what is inside of it (in this case object), you have to check each element in the condition.
A short-way to do that, would be using the some method like this:
<div *ngIf="arrayOfObjects.some(({ name }) => name == 'red')"></div>
The some will return true if some element in arrayOfObjects satisfy this condition. Other way, would be to map the arrayOfObjetcs to arrayOfNames and them check if that array contains name, like this:
arrayOfObjects.map(({ name }) => name).contains('red')

Mutating object properties within an array with Polymer

I not sure how to solve this issue. I am sure someone will know this very quickly.
I have an array of objects and modifying a property. I have a firebase listener 'child_changed'. When firebase is updated need to update the array. Here is the code below.
dbRefList.on('child_changed', function(snap) {
var len = this.grocerylist.length;
for(var i=len; i--;) {
if(this.grocerylist[i].key === snap.key) {
this.set(['grocerylist', i, 'selected'], snap.val().selected);
}
}
this.notifyPath('grocerylist', this.grocerylist.slice());
}.bind(this));
When the array is modified I want the template repeat-dom to trigger. I know this.set will not trigger array mutation sub properties but again I am not sure how to solve this. I done research and tried so many solutions.
I can force a render on the template dom-repeat but I would prefer the data binding way.
So this code (just the this.set you have in there now) should cause the value of grocerylist.i.selected to update inside the dom-repeat (assuming it's bound in there so it's actually showing up).
What behavior are you seeing? Are you trying to filter or sort the list based on the selected value? In that case, you might need to add observe="selected" on the dom-repeat.
(Also—have you confirmed that the child-changed callback is being called with the this value you expect—the element—rather than window or something else?)
You should be able to force a refresh by doing this.grocerylist = this.grocerylist.slice() or this.set('grocerylist', this.grocerylist.slice()); ... notifyPath doesn't work here because notifyPath doesn't change the value, it notifies the element about a prior change (the second argument is effectively ignored).

Angular filter works but causes "10 $digest iterations reached"

I receive data from my back end server structured like this:
{
name : "Mc Feast",
owner : "Mc Donalds"
},
{
name : "Royale with cheese",
owner : "Mc Donalds"
},
{
name : "Whopper",
owner : "Burger King"
}
For my view I would like to "invert" the list. I.e. I want to list each owner, and for that owner list all hamburgers. I can achieve this by using the underscorejs function groupBy in a filter which I then use in with the ng-repeat directive:
JS:
app.filter("ownerGrouping", function() {
return function(collection) {
return _.groupBy(collection, function(item) {
return item.owner;
});
}
});
HTML:
<li ng-repeat="(owner, hamburgerList) in hamburgers | ownerGrouping">
{{owner}}:
<ul>
<li ng-repeat="burger in hamburgerList | orderBy : 'name'">{{burger.name}}</li>
</ul>
</li>
This works as expected but I get an enormous error stack trace when the list is rendered with the error message "10 $digest iterations reached". I have a hard time seeing how my code creates an infinite loop which is implied by this message. Does any one know why?
Here is a link to a plunk with the code: http://plnkr.co/edit/8kbVuWhOMlMojp0E5Qbs?p=preview
This happens because _.groupBy returns a collection of new objects every time it runs. Angular's ngRepeat doesn't realize that those objects are equal because ngRepeat tracks them by identity. New object leads to new identity. This makes Angular think that something has changed since the last check, which means that Angular should run another check (aka digest). The next digest ends up getting yet another new set of objects, and so another digest is triggered. The repeats until Angular gives up.
One easy way to get rid of the error is to make sure your filter returns the same collection of objects every time (unless of course it has changed). You can do this very easily with underscore by using _.memoize. Just wrap the filter function in memoize:
app.filter("ownerGrouping", function() {
return _.memoize(function(collection, field) {
return _.groupBy(collection, function(item) {
return item.owner;
});
}, function resolver(collection, field) {
return collection.length + field;
})
});
A resolver function is required if you plan to use different field values for your filters. In the example above, the length of the array is used. A better be to reduce the collection to a unique md5 hash string.
See plunker fork here. Memoize will remember the result of a specific input and return the same object if the input is the same as before. If the values change frequently though then you should check if _.memoize discards old results to avoid a memory leak over time.
Investigating a bit further I see that ngRepeat supports an extended syntax ... track by EXPRESSION, which might be helpful somehow by allowing you to tell Angular to look at the owner of the restaurants instead of the identity of the objects. This would be an alternative to the memoization trick above, though I couldn't manage to test it in the plunker (possibly old version of Angular from before track by was implemented?).
Okay, I think I figured it out. Start by taking a look at the source code for ngRepeat. Notice line 199: This is where we set up watches on the array/object we are repeating over, so that if it or its elements change a digest cycle will be triggered:
$scope.$watchCollection(rhs, function ngRepeatAction(collection){
Now we need to find the definition of $watchCollection, which begins on line 360 of rootScope.js. This function is passed in our array or object expression, which in our case is hamburgers | ownerGrouping. On line 365 that string expression is turned into a function using the $parse service, a function which will be invoked later, and every time this watcher runs:
var objGetter = $parse(obj);
That new function, which will evaluate our filter and get the resulting array, is invoked just a few lines down:
newValue = objGetter(self);
So newValue holds the result of our filtered data, after groupBy has been applied.
Next scroll down to line 408 and take a look at this code:
// copy the items to oldValue and look for changes.
for (var i = 0; i < newLength; i++) {
if (oldValue[i] !== newValue[i]) {
changeDetected++;
oldValue[i] = newValue[i];
}
}
The first time running, oldValue is just an empty array (set up above as "internalArray"), so a change will be detected. However, each of its elements will be set to the corresponding element of newValue, so that we expect the next time it runs everything should match and no change will be detected. So when everything is working normally this code will be run twice. Once for the setup, which detects a change from the initial null state, and then once again, because the detected change forces a new digest cycle to run. In the normal case no changes will be detected during this 2nd run, because at that point (oldValue[i] !== newValue[i]) will be false for all i. This is why you were seeing 2 console.log outputs in your working example.
But in your failing case, your filter code is generating a new array with new elments every time it's run. While this new array's elments have the same value as the old array's elements (it's a perfect copy), they are not the same actual elements. That is, they refer to different objects in memory that simply happen to have the same properties and values. Hence in your case oldValue[i] !== newValue[i] will always be true, for the same reason that, eg, {x: 1} !== {x: 1} is always true. And a change will always be detected.
So the essential problem is that your filter is creating a new copy of the array every time it's run, consisting of new elements that are copies of the original array's elments. So the watcher setup by ngRepeat just gets stuck in what is essentially an infinite recursive loop, always detecting a change and triggering a new digest cycle.
Here's a simpler version of your code that recreates the same problem: http://plnkr.co/edit/KiU4v4V0iXmdOKesgy7t?p=preview
The problem vanishes if the filter stops creating a new array every time it's run.
New to AngularJS 1.2 is a "track-by" option for the ng-repeat directive. You can use it to help Angular recognize that different object instances should really be considered the same object.
ng-repeat="student in students track by student.id"
This will help unconfuse Angular in cases like yours where you're using Underscore to do heavyweight slicing and dicing, producing new objects instead of merely filtering them.
Thanks for the memoize solution, it works fine.
However, _.memoize uses the first passed parameter as the default key for its cache. This could not be handy, especially if the first parameter will always be the same reference. Hopefully, this behavior is configurable via the resolver parameter.
In the example below, the first parameter will always be the same array, and the second one a string representing on which field it should be grouped by:
return _.memoize(function(collection, field) {
return _.groupBy(collection, field);
}, function resolver(collection, field) {
return collection.length + field;
});
Pardon the brevity, but try ng-init="thing = (array | fn:arg)" and use thing in your ng-repeat. Works for me but this is a broad issue.
I am not sure why this error is coming but, logically the filter function gets called for each element for the array.
In your case the filter function that you have created returns a function which should only be called when the array is updated, not for each element of the array. The result returned by the function can then be bounded to html.
I have forked the plunker and have created my own implementation of it here http://plnkr.co/edit/KTlTfFyVUhWVCtX6igsn
It does not use any filter. The basic idea is to call the groupBy at the start and whenever an element is added
$scope.ownerHamburgers=_.groupBy(hamburgers, function(item) {
return item.owner;
});
$scope.addBurger = function() {
hamburgers.push({
name : "Mc Fish",
owner :"Mc Donalds"
});
$scope.ownerHamburgers=_.groupBy(hamburgers, function(item) {
return item.owner;
});
}
For what it's worth, to add one more example and solution, I had a simple filter like this:
.filter('paragraphs', function () {
return function (text) {
return text.split(/\n\n/g);
}
})
with:
<p ng-repeat="p in (description | paragraphs)">{{ p }}</p>
which caused the described infinite recursion in $digest. Was easily fixed with:
<p ng-repeat="(i, p) in (description | paragraphs) track by i">{{ p }}</p>
This is also necessary since ngRepeat paradoxically doesn't like repeaters, i.e. "foo\n\nfoo" would cause an error because of two identical paragraphs. This solution may not be appropriate if the contents of the paragraphs are actually changing and it's important that they keep getting digested, but in my case this isn't an issue.

Resources