Create a immutable copy of a property - javascript

I have a situation where I want to make a unchangeable copy of a property to restore state to its original... well state.
I have a array of group objects.
Inside each group i have and array of items.
When I make the copy bellow everything is fine.
I start by doing this.
componentDidMount(){
// originalGroups = Object.assign([], this.props.modalitygroups);
originalGroups = JSON.parse(JSON.stringify(this.props.modalitygroups));
},
I have tried both of the statements above, but have read that the current active one makes a true deep copy of a object. Needles to say it does copy it properly.
I then have THIS search feature to search for items in the groups and items.
_searchFilter:function(search_term){
this.setState({modalitygroups:originalGroups});
let tempGroups = Object.assign([], this.state.modalitygroups);
if(search_term !== ''){
for (let x = (tempGroups.length) - 1; x >= 0; x--)
{
console.log("originalGroups",x,originalGroups);
for (let i = (tempGroups[x].items.length) - 1; i >= 0; i--)
{
if(!tempGroups[x].items[i].description.toLowerCase().includes(search_term.toLowerCase())){
tempGroups[x].items.splice(i,1);
}
}
if(tempGroups[x].items.length === 0){
tempGroups.splice(x, 1);
}
}
this.setState({modalitygroups:tempGroups});
}
},
So I start of by restoring the original state to enable searching through everything. The search feature loops though each groups and within each group loop I loop through each item deleting items that dont contain the search phrase.
After looping through each item, if no item remain in the group, I remove that group from the group array aswell.
This works well first time arround.
But when I start searching for a new item, I find that the originalGroups has changed aswell. The previous deleted items has been removed from the unchangable copy aswell and I dont know why. Where and why does it change my safe copy?
Hope this makes sense.

So modality groups contains original groups? This is hard to follow... Instead of 'saving' the original groups, I'd leave this.props.modalitygroups alone and copy to a filteredGroups of the state. You can reload that from the props that you never change.
In your filter function when you do let tempGroups = Object.assign([], this.state.modalitygroups); that should probably be where you use json to create a deep copy. That is filling the new array with the same group references in the old array, so you are modifying the same group instance in the original.
_searchFilter:function(search_term){
// deep copy
let tempGroups = JSON.parse(JSON.stringify(this.props.modalitygroups));
if(search_term !== ''){
for (let x = (tempGroups.length) - 1; x >= 0; x--)
{
console.log("originalGroups",x,originalGroups);
for (let i = (tempGroups[x].items.length) - 1; i >= 0; i--)
{
if(!tempGroups[x].items[i].description.toLowerCase().includes(search_term.toLowerCase())){
tempGroups[x].items.splice(i,1);
}
}
if(tempGroups[x].items.length === 0){
tempGroups.splice(x, 1);
}
}
this.setState({modalitygroups:tempGroups});
}
},

const state = {
property1: 42
};
const originalGroups = Object.freeze(state);
originalGroups.property1 = 33;
// Throws an error in strict mode
console.log(originalGroups.property1);
// expected output: 42
Essentially ReactJS is still javascript afterall, so you can apply Object.freeze to save a copy of state

Related

How to copy the values of an array (not pointing). Slice isn't working for me

I have a function that tries to systematically add arrays to another multidimensional array. At each step of the way the arrays being added are calculated correctly, however, these calculations change the previously entered values. I've tried using slice but I'm clearly doing it wrong :(.
Please see code below - it is the return posMatrix that is being affected.
function allPossibilities(hand) {
var startingHandLength = hand.length;
var potHand = Array.prototype.slice.call(hand);
var scores = new Array();
var posMatrix = new Array();
var nextCard = 1;
var progressStage = true;
var finished = false;
var shallowArr = new Array();
do {
scores = calculateScores(potHand);
var maxScore = Math.max.apply(null, scores)
shallowArr = potHand.slice();
if (maxScore>16.5)
{posMatrix.push([shallowArr,maxScore])
console.log(posMatrix);
debugger;
if (potHand.length !== startingHandLength)
{
do{
if(potHand[potHand.length-1][1] < 10)
{
potHand[potHand.length-1][1]++;
progressStage = true;
}
else {potHand.pop();
potHand[potHand.length-1][1]++;}
}
while(progressStage === false)
}
}
else
{
potHand.push(["Imaginary",1,"Imaginary"]);
}
progressStage=false;
if(potHand.length === startingHandLength)
{finished = true;}
}
while(finished === false);
return posMatrix;
}
If the starting hand > 16.5, the function works as none of the other code gets to run. But otherwise it does not. The final return should be an array where each element is looks like this: [[array],number]. The number seems to come out fine, but since it is not an object it is not affected. I would expect the [array]s to be different from one another, currently they are all the same.
Slice returns a shallow copy of array, since you have multidimensional array so you need to deep clone of array
JSON.parse(JSON.stringify(array))
Or you can use loadash cloneDeep
You made a shallow copy of hand (which, BTW, you should've included). With statements like this
potHand[potHand.length-1][1]++;
you're accessing and modifying elements of hand, too.
Here, potHand[potHand.length-1] is an object, and it's en element of hand (not a copy - the same element).

How to detect duplicate characters in an Array, and create an argument accordingly?

Good evening, I attempting to detect duplicate characters in a string. More specifically, I am trying to find up to two different duplicates within an Array. If there is one duplicate, add a sub-string, and if there is another duplicate, add a different sub-string. Is there any way to do this?
Here is some example code I have so far:
var CodeFieldArray = ["Z80.0", "Z80.1", "Z80.0", "Z70.4"];
/* We have an array here used to create the final string at the end of the
code. It is a dummy array with similar variables in my actual code. For
reference sake, there may be only one object in the array, or 7 total,
depending on the user's input, which is where the duplicate detection should
come in, in case the user enters in multiples of the same code. */
var i, Index;
for (i = 0, L = 0; i < CodeFieldArray.length; i++) {
Index = CodeFieldArray[i].indexOf(CodeFieldArray[i]);
if(Index > -1) L += 1;
Extra0 = CodeFieldArray.indexOf("Z80.8");
Extra1 = CodeFieldArray.indexOf("Z80.9");
if(L >= 2 && Extra0 == -1) CodeFieldArray.push("Z80.8");
Extra0 = CodeFieldArray.indexOf("Z80.8");
if(L >= 4 && Extra0 != -1 && Extra1 == -1) CodeFieldArray.push("Z80.9");
console.println(Extra0);
}
/*^ we attempted to create arguments where if there are duplicates
'detected', it will push, "Z80.8" or, "Z80.9" to the end of the Array. They
get added, but only when there are enough objects in the Array... it is not
actually detecting for duplicates within the Array itself^*/
function UniqueCode(value, index, self) {
return self.indexOf(value) === index;
}
CodeFieldArray = CodeFieldArray.filter(UniqueCode);
FamilyCodes.value = CodeFieldArray.join(", ");
/* this is where we turn the Array into a string, separated by commas. The expected output would be "Z80.0, Z80.1, Z70.4, Z80.8"*/
I have it to where it will add "Z80.8" or "z80.9" if they are not present, but they are being added, only if there are enough objects in the Array. My for-loop isn't detecting specifically the duplicates themselves. If there was a way to detect specifically the duplicates, and create an argument based off of that, then we would be doing grand. The expected output would be "Z80.0, Z80.1, Z70.4, Z80.8"
You can use Set and forEach and includes
var CodeFieldArray = ["Z80.0", "Z80.1", "Z80.0", "Z70.4"];
let unique = [...new Set(CodeFieldArray)];
let match = ['Z80.8','Z80.9'];
let numOfDup = CodeFieldArray.length - unique.length;
if(numOfDup){
match.forEach(e=>{
if(!unique.includes(e) && numOfDup){
unique.push(e);
numOfDup--;
}
})
}
console.log(unique.join(','))
So the idea is
Use Set to get unique values.
Now see the difference between length of original array and Set to get number of duplicates.
Now will loop through match array and each time we push item from match array into unique we reduce numOfDup by so ( to handle case where we have only one duplicate or no duplicate ).
In the end join by ,
You could do something like this:
var uniqueArray = function(arrArg) {
return arrArg.filter(function(elem, pos,arr) {
return arr.indexOf(elem) == pos;
});
};
uniqueArray ( CodeFieldArray )

How to add/remove elements from array based on array contents

I've been struggling with this piece for a few days and I can't seem to find what's wrong. I have an array with a few objects:
myMainPostObj.categories = [Object, Object]
This is for add/removing categories to a post. Imagine I'm editing an existing post which is already associated with a couple of categories (as per code above).
I also have an array which has all categories in the db (including the ones which are associated with the post). On my js controller I have the following code:
$scope.addCategory = function (cat) {
for (var i in $scope.post.category_ids) {
if (cat._id === $scope.post.category_ids[i]) {
$scope.post.category_ids.slice(i, 1);
} else if (cat._id !== $scope.post.category_ids[i]) {
$scope.post.category_ids.push(cat._id);
}
}
}
The above function is called every time the user click on a category. The idea is for the above function to loop through the categories within the post (associated with the post) and compares it with the category passed as argument. If it matches the function should remove the category. If it doesn't it should add.
In theory this seems straight forward enough, but for whatever reason if I tick on category that is not associated with the post, it adds two (not one as expected) category to the array. The same happens when I try to remove as well.
This is part of a Angular controller and the whole code can be found here
The error in your code is that for each iteration of the loop you either remove or add a category. This isn't right... You should remove if the current id matches but add only if there was no match at all. Something like this:
$scope.addCategory = function (cat) {
var found = false;
for (var i in $scope.post.category_ids) {
if (cat._id === $scope.post.category_ids[i]) {
$scope.post.category_ids.splice(i, 1); // splice, not slice
found = true;
}
}
if (!found) // add only if it wasn't found
$scope.post.category_ids.push(cat._id);
}
I guess the problem could be that you're altering the category_ids array while you're iterating over it with the for loop. You might be better off trying something like this:
$scope.addCategory = function (cat) {
var catIndex = $scope.post.category_ids.indexOf(cat._id);
if (catIndex > -1)
$scope.post.category_ids.splice(catIndex, 1);
else
$scope.post.category_ids.push(cat._id);
}
Note that indexOf doesn't seem to be supported in IE7-8.
Let's simplify this a bit:
const CATEGORIES = [1, 2, 3];
let addCategory = (postCategories, categoryId) => {
CATEGORIES.forEach((cId, index) => {
if (postCategories[index] === cId) console.log("Removing", categoryId);
else console.log("Adding", categoryId);
});
return postCategories;
};
Please ignore the fact that we actually are not mutating the array being passed in.
A is either equal or not equal to B - there is no third option (FILE_NOT_FOUND aside). So you are looping over all of your categories and every time you don't find the category ID in the array at the current index you add it to the postCategories array.
The proper solution to your problem is just to use a Set (or if you need more than bleeding edge ES6 support, an object with no prototype):
// Nicer, ES6-or-pollyfill option
var postCategories = new Set();
postCategories.add(categoryId);
// Or, in the no-prototype option:
var postCategories = Object.create(null);
postCategories[categoryId] = true;
// Then serialization needs an extra step if you need an array:
parentObject.postCategories = Object.keys(parentObject.postCategories);

How to remove an array element without splice and without undefined remaining?

Is it possible to remove an array element at a certain position, without rearranging indexes, and without that position changing to undefined?
I don't think that is possible with delete nor splice?
I need an accurate way to view the length of the array, without rearranging indexes.
I do not want to use splice because i have an object that has specific positions mapped to actual X,Y points of a tabel (Punkt).
UPDATE: actually, knowing if the array element exists out of ONLY undefined values might also help me, is there an easier way then looping through?
var keys = Object.keys(racks);
for (var i = 0; i < keys.length; i++)
{
for (var x = 0; x < racks[keys[i]].punkt.length; x++)
{
if(racks[keys[i]].punkt[x].y == fullName)
{
//delete racks[keys[i]].punkt[x];
racks[keys[i]].punkt.splice(x,1);
console.log(keys[i] + " : " + racks[keys[i]].punkt.length);
}
}
}
I don't think that is possible with delete nor splice?
I need an accurate way to view the length of the array, without rearranging indexes.
Then delete, a hasOwnProperty or in guard when retrieving from the array, and a loop counting the elements (or a separate variable keeping track) is the only way to do it. JavaScript's standard arrays are inherently sparse (because they're not really arrays at all), they can have gaps in them where they don't have entries. To create a gap, delete the array entry using delete.
Example:
// Setup
var a = ["a", "b", "c", "d"];
console.log(a.length); // 4
// Using delete
delete a[2]; // Delete the entry containing "c"
console.log(a.length); // Still 4
a.hasOwnProperty(2); // false
// Using the guard when getting an entry
if (a.hasOwnProperty(2)) { // Or `if (2 in a)`
// Get and use [2]
}
else {
// Do whatever it is you want to do when the array doesn't have the entry
}
// Finding out how many it *really* has:
var key;
var count = 0;
for (key in a) {
if (a.hasOwnProperty(key) && // See below
/^0$|^[1-9]\d*$/.test(key) &&
key <= 4294967294
) {
++count;
}
}
console.log(count); // 3
See this other answer for the details behind that if in the loop. If you never put non-element properties on the array, you can skip the second and third parts of that if.
This works perfectly.
var delrow = window.event.srcElement;
while ((delrow = delrow.parentElement) && delrow.tagName != "TR");
delrow.parentElement.removeChild(delrow);
var punten = racks[keys[i]].punkt.length;
if(racks[keys[i]].punkt[x].y == fullName)
{
delete racks[keys[i]].punkt[x];
punten--;
}
if(punten==0)
{
console.log("Could have removed device: " + keys[i]);
}

Store a table row index as an array index

There a simple function:
selected_row = []; // global scope
function toggleRowNumber(rowIndex) {
if(selected_row[rowIndex]) selected_row.splice(rowIndex, 1);
else selected_row[rowIndex] = 1;
}
usage
toggleRowNumber(50000); // click the row - write the index
toggleRowNumber(50000); // click the row again - remove the inxed
alert(selected_row.length);
50001
OK
Delightful feature!
So is there a way to direct write|read an index without any searchin/looping? And without this huge feat as decribed above.
Thanks.
If I understoold correctly, you want to store and index where you can check/set whether an item is selected or not. If that is the case, you are looking for a "key - value" data structure. Then, why not use a map?
var selected_row = {};
function toggleRowNumber(rowIndex) {
if(selected_row[rowIndex]) selected_row[rowIndex] = 0; //or = undefined;
else selected_row[rowIndex] = 1;
}
That is better because hash map will save you time and space.
Space becuase you are not storing hundreds of 'undefined' values in a vector.
Time because, hash function used to access elements is pretended to hit the right position in many cases.

Categories