What is the reason behind this code working
return state.update(action.id, function(item) {
return {id: item.id, title: item.title, duration: item.duration - 1 };
});
and this one that does not work?
return state.update(action.id, function(item) {
item.duration = item.duration - 1;
return item;
});
What is the main difference?
ImmitableJS's List is not "deeply immutable". item is a reference. For you to end up with a new List reference after this operation (and your React components to know something changed) the List references themselves need to change, not just data inside the objects referenced by items.
The reason your first example works is that you are dropping a reference from the List and adding a new one, meaning you get a new List (different reference).
The second example does not alter the references themselves, just the data in the objects, thus you won't get a new List reference.
You could initialize this List by doing a Immutable.fromJS() to get your initial List instance. This one is going to create a "deeply immutable" List that will behave like you expect it to in your second example.
Try this (here's the fiddle for it):
var list = Immutable.List([ {id: 1}, {id: 2}, {id: 3} ]);
var deepList = Immutable.fromJS([ {id: 1}, {id: 2}, {id: 3} ]);
var mutatedList1 = list.update(0, function(item) {
item.id = 'x';
return item;
});
var mutatedList2 = list.update(0, function(item) {
return {id: 'x' };
});
var mutatedList3 = deepList.update(0, function(item) {
return {id: 'x' };
});
console.log(list.get(0), mutatedList1.get(0), list.get(0) === mutatedList1.get(0), list === mutatedList1);
console.log(list.get(0), mutatedList2.get(0), list === mutatedList2);
console.log(deepList === mutatedList3);
With this output:
Object {id: "x"} Object {id: "x"} true true
Object {id: "x"} Object {id: "x"} false
false
For React to know your list changed the comparison has to be false.
Related
Even replicating the object succeeded. However, the id value is duplicated the same. Therefore, if you click the delete button, the deletion will be done together.But I want to get a different ID. How can I fix it here?
const handleduplicate = id => {
const dupDataGroup = [...Groupdata].filter(item => item.id === id);
const newGroup = [dupDataGroup[0], ...Groupdata];
setGroupData(newGroup);
};
The result when I didn't do anything =>
0: {id: 0, title: 'blur', subtitle: 'Img'}
1: {id: 1, title: 'blurs', subtitle: 'Ima'}
2: {id: 2, title: 'Free ', subtitle: 'Ima'}
handleduplicate click result=>
0: {**id: 0**, title: 'blur', subtitle: 'Img'}
1: {**id: 0,** title: 'blur', subtitle: 'Img'}
2: {id: 1, title: 'blurs', subtitle: 'Ima'}
3: {id: 2, title: 'Free ', subtitle: 'Ima'}
I hope that only the ID value will change when duplicated.
React does not automatically update the key index in JSON objects. Also, in this scenario, you're fetching the value and inserting it into the newGroup.
If you really want to update the keys, I'd suggest creating a for loop to update the values of id.
const handleduplicate = id => {
const dupDataGroup = [...Groupdata].filter(item => item.id === id);
const newGroup = [dupDataGroup[0], ...Groupdata];
for (var i = 0; i < newGroup.length; i++)
newGroup[i].id = i;
setGroupData(newGroup);
};
You can duplicate an object by using the spread operator.
const obj = {name: 'ritik', surname: 'banger'};
const objCopy = {...obj};
or use of Object.assign will work for you.
let objcopy = Object.assign({}, obj);
You must note that here we are referencing the original object 'obj' and thus any changes in 'objcopy' will reflect in 'obj', this is known as shallow copy.
If you want to deep copy the object and you wish to have different reference for both then,
Using JQuery library:
import jQ from "jquery";
let objcopy = jQ.extend(true, obj, {});
Using lodash library:
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
[...Groupdata] creates a new list with the (non-duplicated) items in Groupdata. This is important if you'd want to add the item twice to the list, as changing one with change the same object as you already noticed. You will also have to duplicate the item itself.
Note that duplicating the list itself isn't necessary if you're just going to filter on it.
What I also think is happening, given the additional information in the comments, is that setGroupData is used multiple times. If you use Groupdata, followed by setGroupData, then Groupdata is not updated accordingly. So just make sure you use that function once.
const handleduplicate = (groupData, id, newId) => {
const dupDataGroup = groupData.filter(item => item.id === id); // no copying here.
const rest = groupData.filter(item => item.id !== id);
const newGroup = [
{ // the item is duplicated (note: no deep copy)
...dupDataGroup[0],
id: newId // updated with the new identifier
},
...rest
];
return newGroup;
};
const duplicated = handleduplicate(Groupdata, 123, 456);
setGroupData(duplicated);
I have two json objects (data1 and data2) that have related information. Namely, both objects have properties (arrays) which in turn can have identical data. So, I am trying to figure out how to display those data with highlighting them properly, i.e. identical data with green color and non-identical with red color. Somehow it wrongly highlights all data with red color.
Here is the html:
<ul>
<li ng-repeat="item in vm.data2.features"
ng-class="vm.data1.features.indexOf(item) !== -1 ? 'check' : 'uncheck'">
<span ng-bind="item.id"></span>
</li>
</ul>
and objects:
vm.data1 = {
id: '4569',
name: 'Given data',
features: [
{id: "TEST_TEXT2", desc: 'smth12'},
{id: "TEST_PPP", desc: 'smthsmthsmth'},
{id: "TEST_ECASH", desc: "somelongtexthere"}
]
};
vm.data2 = {
id: '1305',
name: 'Base data',
features: [
{id: "TEST_BP", desc: 'smth'},
{id: "TEST_TEXT2", desc: 'smth12'},
{id: "TEST_PPP", desc: 'smthsmthsmth'},
{id: "TEST_TEXT1", desc: 'blahblah'},
{id: "TEST_ECASH", desc: "somelongtexthere"}
]
};
The full demo is here.
Any help would be appreciated.
Indexof() method will look for similarity in object references not the id itself. findIndex() method can help you here instead.
vm.hasFeature = function(item){
var hasElements= vm.data1.features.findIndex(function(e){
return e.id == item.id;
});
console.log(item, hasElements);
return hasElements;
}
And in html
<li ng-repeat="item in vm.data2.features"
ng-class="vm.hasFeature(item) > -1 ? 'check' : 'uncheck'">
vm.hasFeature = function(item){
var hasElements= vm.data1.features.findIndex(function(e){
return e.id == item.id;
});
console.log(item, hasElements);
return hasElements;
}
CodePen Link: https://codepen.io/anon/pen/ewgLBN?editors=1010
None of the objects will be the same because indexOf(item) will compare object references of item. You'll need to do a deep equals comparison of the items.
i.e.
{id: "TEST_TEXT2", desc: 'smth12'} === {id: "TEST_TEXT2", desc: 'smth12'} // false
vm.data1.features[0] === vm.data1.features[1] // false
Example using lodash would be something like:
_.some(vm.data1.features, otherItem => _.isEqual(item, otherItem))
Because
_.isEqual(vm.data1.features[0], vm.data2.features[1]) // true
Docs for Lodash:
_.some
_.isEqual
I have an Array with Objects. In short
Array =[
{id = 1, products='1,2'} //products consist of String with Products Seperates by','
{id = 2, products='1'}
{id = 3, products='3'}
{id = 4, products='1,2,3'}
{id = 5, products='2,3'}
...
]
SelectedProd = ['2,3']// consists a String as well seperated by ','
This are all Displayed in a Table. Im writing now a Filter to show only the ones which are selected through a MultipleSelect.
Therefore I want filter all Objects where at least one Product is in products
So my Filter is getting the Objects and the Selected Products
.filter('filterByProd', () => {
return (objects,prod) => {
var filtered = [];
/*
FIlter Array
*/
return filtered;
};
});
If a User Selects Product = '2,3' it shall return me the Objects with the id=1/3/4/5..
As well clearly if nothing selected return all.
If possible using lodash but other solutions accepted as well.
The problem I have is the Functions are just listed in lodash Docu and cant read everyone which I need.
At the moment Iam stuck at
.filter('filterByProd', () => {
return (items,prod) => {
var filtered = [];
filtered = _.filter(items,['products',prod]);
return filtered;
};
});
This is giving me out only the exact matches with '2,3' -> Object = id=5 only.
I need something
filtered = findAllWhere(selectedProducts,iterateOverSendObjects(checkProductsOfObjects)+_.ifIsAtLeastOneOfSendProducts))
You can use lodash's intersection method to get your desired result.
var a = arr.filter(x => {
return _.intersection(
x.products.split(','), SelectedProd[0].split(',')
).length > 0;
Here's a working inline fiddle:
var arr = [
{id : 1, products:'1,2'},
{id : 2, products:'1'},
{id: 3, products:'3'},
{id : 4, products:'1,2,3'},
{id : 5, products:'2,3'}];
var SelectedProd = ['2,3'];
var a = arr.filter(x => {
return _.intersection(
x.products.split(','), SelectedProd[0].split(',')).length>0;
});
console.log(a);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.4/lodash.js"></script>
Explanation :
Lodash _.intersection returns intersection of two arrays if it exists, or else returns []. We use a filter to filter only those objects where the intersection is non-empty
Once you get this object from the server, modify it, either by injecting a new Array property containing CSV's in Array format or replacing products entirely with an array. Your final array should be something like the following before binding:
[
{id = 1, products=[1,2]},
{id = 2, products=[1]} ... ]
Please mark as answer if this helps
You can filter the items using Array.prototype.filter() with Array.prototype.some() and String.prototype.includes() to search for the selected items in the string:
const findIemsByProducts = (items, selectedProd) => {
const selected = selectedProd[0].split(','); // convert the selected products to array
return items.filter((item) => // filter the items
selected.some((productId) => // if at least one of the selected
item.products.includes(productId) // is in the products string
));
}
var items = [
{id: 1, products: '1,2'},
{id: 2, products: '1'},
{id: 3, products: '3'},
{id: 4, products: '1,2,3'},
{id: 5, products: '2,3'}
];
var SelectedProd = ['2,3'];
var results = findIemsByProducts(items, SelectedProd);
console.log(results);
And the angular filter would be:
.filter('filterByProd', () => (items, selectedProd) => {
const selected = selectedProd[0].split(','); // convert the selected products to array
return items.filter((item) => // filter the items
selected.some((productId) => // if at least one of the selected
item.products.includes(productId) // is in the products string
));
});
I have a collection called `mainItems``
this.mainItems;
which contains 18 models. And also one more collection object which contains selected items:
this.seletedItems;
I need to filter the main collection object based on other collection.
I have tried the below approach:
var that = this;
var model = _.reject(that.filteredItems.models, function(model1){
return _.filter(that.collection.models, function(model2) {
return model1.id == model2.id;
});
});
but this approach is not working properly. Is it possible to filter the main items by avoiding second iteration ?
Please help!
You can use the Underscore's methods proxied by Backbone to simplify your filter.
For example, to list the models in mainItems without the models in selectedItems, you could use
// reject the models found in selectedItems
var filtered = mainItems.reject(function(m) {
return selectedItems.get(m.id);
});
Note that Collection.get is a hash lookup, making this a Backbone equivalent to the answer pointed by #GruffBunny in the comments.
And a demo
var mainItems = new Backbone.Collection([
{id: 1},
{id: 2},
{id: 3},
{id: 4}
]);
var selectedItems = new Backbone.Collection([
{id: 2}
]);
var keep = mainItems.reject(function(m) {
return selectedItems.get(m.id);
});
console.log(_.pluck(keep, 'id'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
You can use difference to get all the models that are not selected.
var mainItems = [{ a: 1 }, { b: 2 }, { c: 3 }];
var selectedItems = mainItems.slice(1);
console.log(_.difference(mainItems, selectedItems));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
I am posting this because I never found a precise answer for filtering nested objects (tree sturcture).
Let's say we have an JSON tree structure that looks like this:
$scope.tree = [{
id: 1,
parent_id: 0,
name: 'Root Item',
items: [
{
id: 2,
parent_id: 1,
name: '1st Child of 1'
},
{
id: 3,
parent_id: 1,
name: '2nd Child of 1'
},
{
id: 4,
parent_id: 1,
name: '3rd Child of 1',
items:[
{
id:5,
parent_id: 4,
name:'1st Child of 5'
},
{
id:6,
parent_id: 4,
name:'2nd Child of 5'
}
]}
]
}]
How do we traverse the tree with a filter to get object with id 6 for example?
If we use the following filter for example:
<div data-ng-init="selectedItem = (tree | filter:{id:6})">
<h1>The name of item with id:6 is selectedItem.name</h1>
</div>
It will only iterate through the first level in which will only find id:1.
So, in order to get nested level objects we must use a recursive filter like this one:
angular.module("myApp",[])
.filter("filterTree",function(){
return function(items,id){
var filtered = [];
var recursiveFilter = function(items,id){
angular.forEach(items,function(item){
if(item.id === id){
filtered.push(item);
}
if(angular.isArray(item.items) && item.items.length > 0){
recursiveFilter(item.items,id);
}
});
};
recursiveFilter(items,id);
return filtered;
};
});
});
So, to use this filter in the markup you would call it like this:
<div data-ng-init="selectedItem = (tree | filterTree:6)">
<h1>The name of item with id:6 is selectedItem.name</h1>
</div>
Hope you find this useful, it took me some time to digest recursive filters.
Of course, this filter works to get 1 item since it returns [0] first object of filtered array. But if you want it to return more than 1 result you'll have to remove only that [0] at the return function and then use ng-repeat to iterate over filtered resutls.