How am I failing to update a state variable in ReactJS? - javascript

I have an event handler in ReactJS designed so that when someone clicks the "Done" checkbox for a calendar entry, the calendar entry is still in the system but it is marked hidden as something not to show. (The calendar is made to allow both one-off and recurring entries, and allow both "Mark this instance done" and "Hide this series" options for recurring entries, but this detail does not concern me here.)
My event handler is intended to copy an array in this.state, makes a clone, push()es an item, and saves the mutated clone as a perhaps cumbersome workaround for directly push()ing an item to an array under this.state. Based on the included console.log() statements, it appears that I am successfully obtaining the item (an integer, here equal to 1), successfully cloning a now-empty array and push()ing the integer into the cloned array, and then failing to modify the empty this.state.hidden_entries.
My code reads:
hide_instance: function(eventObject) {
console.log(eventObject);
var id = parseInt(eventObject.target.id.split('.')[1]);
console.log(id);
// var id = eventObject.target.id;
console.log(this.state.hidden_instances);
console.log('Cloned: ');
var hidden_instances = clone(this.state.hidden_instances);
console.log(hidden_instances);
hidden_instances.push(id);
console.log('Before setState()');
console.log(hidden_instances);
this.setState({'hidden_instances': hidden_instances});
console.log(this.state.hidden_instances);
console.log('After setState()');
this.forceUpdate();
},
In my console.log() output I have:
site.js:350 SyntheticEvent {dispatchConfig: Object, dispatchMarker: ".0.3.2:1.1.0.0.0", nativeEvent: MouseEvent, type: "click", target: input#hide-2015-9-18.1.hide-instance…}
site.js:352 1
site.js:354 []
site.js:355 Cloned:
site.js:357 []
site.js:359 Before setState()
site.js:360 [1]
site.js:362 []
site.js:363 After setState()
My console.log() statements in order say "Before setState()", log the mutated clone, attempt to assign the mutated clone the correct way to this.state.hidden_instances, and then reads back the state variable that was just set, only to see that it remains unchanged.
What should I be doing differently, in this case, to append an item to this.state.hidden_instances, and why is my code failing to mutate the value at that location?
--UPDATE--
I'll post the clone() function, intended for JSON-serializable objects only, but it appears to be returning an empty array when given an empty array; the only way I can see a problem is if it's returning the same empty array instead of a new one. But in case I missed something, here it is:
var clone = function(original) {
if (typeof original === 'undefined' ||
original === null ||
typeof original === 'boolean' ||
typeof original === 'number' ||
typeof original === 'string') {
return original;
}
if (Object.prototype.toString.call(original) === '[object Array]') {
var result = [];
for(var index = 0; index < original.length; index += 1) {
result[index] = clone(original[index]);
}
} else {
var result = {};
for(var current in original) {
if (original.hasOwnProperty(current)) {
result[current] = original[current];
}
}
}
if (typeof original.prototype !== 'undefined') {
result.prototype = original.prototype;
}
return result;
}

From the documentation (in a big red box nonetheless):
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
and
The second (optional) parameter is a callback function that will be executed once setState is completed and the component is re-rendered.
That callback gets passed the updated state afaik.

Related

check for object is undefined works only on first array item

I'm trying to check if the object properties 'compliancestatus' and 'comments' are empty, undefined, undeclared i.e. no real value.
I am first checking that the item exists in the object array and my console.log shows that the values do exist and that both object properties are undefined.
The issue is that the second object triggers the else (obj array after2) somehow...
My object is created using:
// Create the object.
let contentObj = new Object();
contentObj.prefix = clausePrefix;
contentObj.no = clauseNo;
if (clauseComplianceStatus != null) {
contentObj.compliancestatus = clauseComplianceStatus;
}
if (clauseComments != null) {
contentObj.comments = clauseComments;
}
code
// Check if current iteration exists in the content array.
if (content.some(e => e.prefix === tempOBJ.content[y].prefix && e.no === tempOBJ.content[y].no)) {
// THIS WORKS FOR ALL OBJECTS
let currentIterationIndexOfClauseNo = content.findIndex(e => e.prefix === tempOBJ.content[y].prefix && e.no === tempOBJ.content[y].no);
if (!content[currentIterationIndexOfClauseNo].compliancestatus && !content[currentIterationIndexOfClauseNo].comments) {
// THIS WILL ONLY WORK FOR THE FIRST OBJECT
} else {
// SECOND OBJECT EXECUTES HERE FOR SOME REASONS DESPITE HAVING BOTH VALUES 'undefined' THE SAME AS THE FIRST OBJECT.
}
}
I can't comment, so I'll have to ask this way. Have you checked if your currentIterationIndexOfClauseNo actually changes? The code is still a little bit hard to understand, but I have one small suggestion to make (no solution but makes the code a bit simpler). Are the values of the first object also both undefined?
const obj = content.find(e => e.prefix === tempOBJ.content[y].prefix && e.no === tempOBJ.content[y].no); // will be undefined if nothing was found
if (obj !== undefined) {
if (!obj.compliancestatus && !obj.comments) {
// THIS WILL ONLY WORK FOR THE FIRST OBJECT
} else {
// SECOND OBJECT EXECUTES HERE FOR SOME REASONS DESPITE HAVING BOTH VALUES 'undefined' THE SAME AS THE FIRST OBJECT.
}
}

Is there a way to change the original reference to an object in a function call

I was working with an array in JavaScript and was wondering why changes I made to an array were correctly saving to localStorage, but weren't being reflected in the array past the function call. The code was the following:
function removeFromList(array, arrayName, key) {
array = array.filter(function(element) { return element.key !== key; });
localStorage.setItem(arrayName, JSON.stringify(array));
}
I did some googling and, through some old posts, discovered that the array was being passed by value to the function, that is, the array variable, which pointed to an array object, was being passed by value, and changing that copy did not affect the original variable that was pointing to my array object.
I came up with the following code as a workaround:
function removeFromList(array, arrayName, key) {
arrayTemp = array.filter(function(element) { return element.key !== key; });
for(var i = 0; i < array.length; i++) {
if (!arrayTemp.some(item => item.key === array[i].key)) {
array.splice(i, 1);
}
}
localStorage.setItem(arrayName, JSON.stringify(array));
}
This solved my problem, and the new contents of the array was displayed in both localStorage and the array object that was pointed to by the original variable. However, I've been wondering if there is some new way introduced into JavaScript recently or an older method I did not find that would do a better job of achieving the desired result.
I did some googling and, through some old posts, discovered that the array was being passed by value to the function, that is, the array variable, which pointed to an array object, was being passed by value, and changing that copy did not affect the original variable that was pointing to my array object.
Exactly right.
Is there a way to change the original reference to an object in a function call
No, JavaScript is still a purely pass-by-value language. While I suppose it's possible for that to change at some point, it hasn't as of this writing (and it seems really unlikely to me it ever will). If you do example(x) when x contains 42 (or an array reference), there still isn't any way for example to reach out and change the value of x (to 43, or to refer to a different array). If x refers to a mutable object (like an array), example can modify that object, but it can't make x refer to a whole new object.
Your workaround works by modifying the existing array. FWIW, in general it would be preferred to return the new array instead, so the caller has the option of either keeping the original or using the new one. E.g.:
function removeFromList(array, arrayName, key) {
array = array.filter(function(element) { return element.key !== key; });
localStorage.setItem(arrayName, JSON.stringify(array));
return array;
}
And then when using it:
variableContainingArray = removeFromList(variableContainingArray, "nameInLocalStorage", 42);
But if you want to update in place, you don't need a temporary array:
function removeFromList(array, arrayName, key) {
// Looping backward we don't have to worry about the indexes when we remove an entry
for (let i = array.length - 1; i >= 0; --i) {
if (array[i].key === key) {
array.splice(i, 1);
}
}
localStorage.setItem(arrayName, JSON.stringify(array));
}
Instead of using a for-loop to remove the values from the argument array you can also empty it out using splice and add the filtered values:
function removeFromList(array, arrayName, key) {
var filtered = array.filter(function(element) { return element.key !== key; });
array.splice(0, array.length, ...filtered);
localStorage.setItem(arrayName, JSON.stringify(array));
}
I suggest changing var to const and function(element) { return element.key !== key; } to element => element.key !== key if those features are available within your runtime environment.

How to check if an object's keys and deep keys are equal, similar to lodash's isEqual?

So I'm in a unique situation where I have two objects, and I need to compare the keys on said objects to make sure they match the default object. Here's an example of what I'm trying to do:
const _ = require('lodash');
class DefaultObject {
constructor(id) {
this.id = id;
this.myobj1 = {
setting1: true,
setting2: false,
setting3: 'mydynamicstring'
};
this.myobj2 = {
perm1: 'ALL',
perm2: 'LIMITED',
perm3: 'LIMITED',
perm4: 'ADMIN'
};
}
}
async verifyDataIntegrity(id, data) {
const defaultData = _.merge(new DefaultObject(id));
if (defaultData.hasOwnProperty('myoldsetting')) delete defaultData.myoldsetting;
if (!_.isEqual(data, defaultData)) {
await myMongoDBCollection.replaceOne({ id }, defaultData);
return defaultData;
} else {
return data;
}
}
async requestData(id) {
const data = await myMongoDBCollection.findOne({ id });
if (!data) data = await this.makeNewData(id);
else data = await this.verifyDataIntegrity(id, data);
return data;
}
Let me explain. First, I have a default object which is created every time a user first uses the service. Then, that object is modified to their customized settings. For example, they could change 'setting1' to be false while changing 'perm2' to be 'ALL'.
Now, an older version of my default object used to have a property called 'myoldsetting'. I don't want newer products to have this setting, so every time a user requests their data I check if their object has the setting 'myoldsetting', and if it does, delete it. Then, to prevent needless updates (because this is called every time a user wants their data), I check if it is equal with the new default object.
But this doesn't work, because if the user has changed a setting, it will always return false and force a database update, even though none of the keys have changed. To fix this, I need a method of comparing the keys on an object, rather any the keys and data.
That way, if I add a new option to DefaultObject, say, 'perm5' set to 'ADMIN', then it will update the user's object. But, if their object has the same keys (it's up to date), then continue along your day.
I need this comparison to be deep, just in case I add a new property in, for example, myobj1. If I only compare the main level keys (id, myobj1, myobj2), it won't know if I added a new key into myobj1 or myobj2.
I apologize if this doesn't make sense, it's a very specific situation. Thanks in advance if you're able to help.
~~~~EDIT~~~~
Alright, so I've actually come up with a function that does exactly what I need. The issue is, I'd like to minify it so that it's not so big. Also, I can't seem to find a way to check if an item is a object even when it's null. This answer wasn't very helpful.
Here's my working function.
function getKeysDeep(arr, obj) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object') {
arr = getKeysDeep(arr, obj[key]);
}
});
arr = arr.concat(Object.keys(obj));
return arr;
}
Usage
getKeysDeep([], myobj);
Is it possible to use it without having to put an empty array in too?
So, if I understand you correctly you would like to compare the keys of two objects, correct?
If that is the case you could try something like this:
function hasSameKeys(a, b) {
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
return aKeys.length === bKeys.length && !(aKeys.some(key => bKeys.indexOf(key) < 0));
}
Object.keys(x) will give you all the keys of the objects own properties.
indexOf will return a -1 if the value is not in the array that indexOf is being called on.
some will return as soon as the any element of the array (aKeys) evaluates to true in the callback. In this case: If any of the keys is not included in the other array (indexOf(key) < 0)
Alright, so I've actually come up with a function that does exactly what I need. The issue is, I'd like to minify it so that it's not so big. Also, I can't seem to find a way to check if an item is a object even when it's null.
In the end, this works for me. If anyone can improve it that'd be awesome.
function getKeysDeep(obj, arr = []) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && !Array.isArray(obj[key]) && obj[key] !== null) {
arr = this.getKeysDeep(obj[key], arr);
}
});
return arr.concat(Object.keys(obj));
}
getKeysDeep(myobj);

Javascript Object Assignment Infinite recursion

I have an issue that I am struggling to grasp. Any help would be greatly appreciated.
I have an Object, and I assign the current object state to a property on the current object.
example below:
var product = {
ropeType: 'blah',
ropePrice: 'blah',
ropeSections: {
name: 'blaah',
price: 'blaah'
},
memory: false
}
product.memory = product;
Now when I look at the product object within the console I get a inifinite recursion of Product.memory.Product.memory.Product....
screenshot below:
I know its something to do with that an object references itself, but I cannot seem to grasp the concept. Could someone explain?
The reason I am trying to do something like this is to save in local storage the current state of the object.
I hope I have made sense.
I assign the current object state to a property on the current object.
No, you created a property that referred to itself.
If you want to save the current state of the property then you need to clone the object.
If you want to create a (shallow) copy of an object then you can use:
function clone(obj) {
if(obj === null || typeof(obj) !== 'object' || 'isActiveClone' in obj)
return obj;
var temp = obj.constructor();
for(var key in obj) {
if(Object.prototype.hasOwnProperty.call(obj, key)) {
obj['isActiveClone'] = null;
temp[key] = obj[key];
delete obj['isActiveClone'];
}
}
return temp;
}
[code taken from here - and modified slightly to do a shallow copy rather than recursive deep copy]
then do:
product.memory = clone( product );
You may find you get the issues with recursion if you clone it a second time and it copies the product.memory along with the rest of the object. In that case just delete product.memory before doing subsequent clones.
Something like:
function saveCurrentState( obj ){
if ( 'memory' in obj )
delete obj.memory;
obj.memory = clone( obj );
}
Aside
If you want a deep copy then you can do:
function deepCopy(obj){
return JSON.parse(JSON.stringify(obj));
}
[As suggested here - but note the caveats it has for Date objects]
you could do your idea by clone the current product into new. We've Object.keys to get all attribute of object. So here is my idea :
product = {
ropeType: 'blah',
ropePrice: 'blah',
ropeSections: {
name: 'blaah',
price: 'blaah'
},
memory: false
};
var keys = Object.keys(product);
var newProduct = {};
keys.forEach(function(key){
if(key === 'memory') return;
newProduct[key] = product[key];
});
product.memory = newProduct;
Instead of actually storing a reference to the object, you might want to transform that object's state. Maybe by cloning it onto a new object or possibly keeping it as a JSON string (which you'll want to do if you're using localStorage).
Since you will probably want to see the current state of the object whenever you check the memory property, you should make memory a function that does that transformation.
Maybe something like this:
var product = {
ropeType: 'blah',
ropePrice: 'blah',
ropeSections: {
name: 'blaah',
price: 'blaah'
},
memory: function() {
return JSON.stringify(this);
}
}
You can then call product.memory() and get its state in JSON.
This here is the problem:
product.memory = product;
You're assigning a reference to an object to itself. JavaScript passes objects by reference, so it's never going to store a clone of itself through assignment.
If you're looking to record modifications made to the object over time, the best way would be to use an array to hold cloned copies of it (or at least the properties that've changed).
To give you the quickest example:
var Product = function(){
};
var product = new Product();
product.history = [];
product.saveState = function(){
var changes = {};
for(var i in this){
/** Prevent infinite self-referencing, and don't store this function itself. */
if(this[i] !== this.history && this[i] !== this.saveState){
changes[i] = this[i];
}
}
this.history.push(changes);
};
Obviously, there're many better ways to achieve this in JavaScript, but they require more explanation. Basically, looping through an object to store its properties is inevitably going to trip up upon the property that they're being assigned to, so a check is needed at some point to prevent self-referencing.

Preventing duplicate items in a knockout.js observable array

I run a timed loop which fetches data asynchronously from the server and updates an observable array. I thought that this would prevent dups but it doesn't seem to. How can I prevent adding duplicates?
// Operations
self.addDevice = function (device) {
if (device != null && ko.utils.arrayIndexOf(self.devices, device) < 0) {
self.devices.push(device);
}
}
This is always returning true, as in the array does not contain the particular device (though it clearly does).
The updates you get may have all the same values as objects you have in your array, but they're probably different objects, so a simple equality check will return false. You'll have to supply a callback to test for equality yourself by comparing properties within the objects.
For example, if a = {prop: 5} and b = {prop: 5}, then a == b returns false. You need to pass in a function to ko.utils.arrayFirst or ko.utils.arrayFilter like
var newItem = new Item();
ko.utils.arrayFirst(self.items(), function(existingItem, newItem) {
return existingItem.prop == newItem.prop;
}

Categories