Preventing duplicate items in a knockout.js observable array - javascript

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;
}

Related

Deeply equal function error in Javascript

I am trying to create a function to check whether two arrays are deeply equal to each other.
An example would be: [1, 2, { a: "hello" }] and [1, 2, { a: "bye" }] would return false.
This is my code so far:
const deeplyEquals = (val1, val2) => {
let counter = 0;
for (var i = 0; i < val1.length; i++) {
if (typeof val1[i] === "object") {
deeplyEquals(JSON.stringify(val1[i]), JSON.stringify(val2[i]));
} else if (typeof val2[i] === "object") {
deeplyEquals(JSON.stringify(val1[i]), JSON.stringify(val2[i]));
} else if (val1[i] !== val2[i]) {
counter++;
}
}
return counter === 0 ? true : false;
};
I implemented a counter so that if it found a value in 1 that was not equal to the same value in 2 then it would increment. If the counter was not 0 then it would return false.
For the example, the counter increments to 7 but then right at the end, changes to 0 and therefore returns true instead of false.
I'm sure there would be an easier way to do this but I was wanting to see whether I could make this work as I am unsure why the counter is changing to 0 right at the end.
Thanks for any help!
The problem is that the counter is local to each call to deeplyEquals. There's a different counter for each call, and since you're making the calls recursively, you have lots of different counter variables in memory at the same time.
If you wanted to maintain a counter, you'd have to have each recursive call return the counter value (instead of a flag) so the code calling it could increment its counter by that much.
But there's no need in your code. Instead, just return false the first time you find a difference, either during in the call itself or in one of its recursive calls by checking the return value of the recursive call.
There are other issues with the code. Here's what I notice off-the-cuff:
You're calling JSON.stringify, which returns a string, before passing values to deeplyEquals, which will convert arrays and objects to strings. Comparing the strings won't be reliable (because equivalent objects can have their properties in different orders: JSON.stringify({a:1,b:2}) is the string {"a":1,"b":2}, but JSON.stringify({b:2,a:1}) is the string {"b":2,"a":1}). Instead, pass the actual value.
typeof x returns "object" for arrays and null as well as non-array objects; you need to handle those three cases separately.
When comparing non-array objects, you need to loop through their properties to compare them.
SO has several questions and answers about doing deep equality checks; probably best to search for those, study them to ensure you understand how they work, and go from there.
Why dont you just JSON stringify both arrays and then compare?
const deepEquals(val1, val2){
let v1 = JSON.stringify(val1);
let v2 = JSON.stringify(val2);
return v1 === v2;
}

Array of objects returning null

I have this code that formats an array of objects, which is set out how I want it. However, when I go to return the output something strange happens. If I were to just return alert_cache, it returns null. But If I were return it like alert_cache.workflow_steps it returns the data needed.
Does anyone have any idea how to get around this?
if (alert_cache.length == 0) {
alert_cache.workflow_steps = {}
alert_cache.workflow_steps[keys.workflow_step] = { "errors": [], "last_error": {}};
let alr = alert_cache.workflow_steps[keys.workflow_step];
alr.errors.push(now)
alr.last_error = {message: keys.message, url:alert.step_log_url}
}
return alert_cache;
You're using alert_cache like an array and like an object. You're checking length (as if it were an array):
if (alert_cache.length == 0) {
but you're also assigning to a non-element property:
alert_cache.workflow_steps = {}
Note that doing that will not change length.
You haven't shown how you create alert_cache to start with, but if it's an array, and if you're then using it with something that only looks at its array entries and not at its other properties (for instance, JSON.stringify), it will be empty (not null).

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);

How does shallow compare work in react

In this documentation of React, it is said that
shallowCompare performs a shallow equality check on the current props and nextProps objects as well as the current state and nextState objects.
The thing which I am unable to understand is If It shallowly compares the objects then shouldComponentUpdate method will always return true, as
We should not mutate the states.
and if we are not mutating the states then the comparison will always return false and so the shouldComponent update will always return true. I am confused about how it is working and how will we override this to boost the performance.
Shallow compare does check for equality. When comparing scalar values (numbers, strings) it compares their values. When comparing objects, it does not compare their attributes - only their references are compared (e.g. "do they point to same object?").
Let's consider the following shape of user object
user = {
name: "John",
surname: "Doe"
}
Example 1:
const user = this.state.user;
user.name = "Jane";
console.log(user === this.state.user); // true
Notice you changed users name. Even with this change, the objects are equal. The references are exactly the same.
Example 2:
const user = clone(this.state.user);
console.log(user === this.state.user); // false
Now, without any changes to object properties they are completely different. By cloning the original object, you create a new copy with a different reference.
Clone function might look like this (ES6 syntax)
const clone = obj => Object.assign({}, ...obj);
Shallow compare is an efficient way to detect changes. It expects you don't mutate data.
shallow comparison is when the properties of the objects being compared is done using "===" or strict equality and will not conduct comparisons deeper into the properties. for e.g.
// a simple implementation of the shallowCompare.
// only compares the first level properties and hence shallow.
// state updates(theoretically) if this function returns true.
function shallowCompare(newObj, prevObj){
for (key in newObj){
if(newObj[key] !== prevObj[key]) return true;
}
return false;
}
//
var game_item = {
game: "football",
first_world_cup: "1930",
teams: {
North_America: 1,
South_America: 4,
Europe: 8
}
}
// Case 1:
// if this be the object passed to setState
var updated_game_item1 = {
game: "football",
first_world_cup: "1930",
teams: {
North_America: 1,
South_America: 4,
Europe: 8
}
}
shallowCompare(updated_game_item1, game_item); // true - meaning the state
// will update.
Although both the objects appear to be same, game_item.teams is not the same reference as updated_game_item.teams. For 2 objects to be same, they should point to the same object.
Thus this results in the state being evaluated to be updated
// Case 2:
// if this be the object passed to setState
var updated_game_item2 = {
game: "football",
first_world_cup: "1930",
teams: game_item.teams
}
shallowCompare(updated_game_item2, game_item); // false - meaning the state
// will not update.
This time every one of the properties return true for the strict comparison as the teams property in the new and old object point to the same object.
// Case 3:
// if this be the object passed to setState
var updated_game_item3 = {
first_world_cup: 1930
}
shallowCompare(updated_game_item3, game_item); // true - will update
The updated_game_item3.first_world_cup property fails the strict evaluation as 1930 is a number while game_item.first_world_cup is a string. Had the comparison been loose (==) this would have passed. Nonetheless this will also result in state update.
Additional Notes:
Doing deep compare is pointless as it would significantly effect performance if the state object is deeply nested. But if its not too nested and you still need a deep compare, implement it in shouldComponentUpdate and check if that suffices.
You can definitely mutate the state object directly but the state of the components would not be affected, since its in the setState method flow that react implements the component update cycle hooks. If you update the state object directly to deliberately avoid the component life-cycle hooks, then probably you should be using a simple variable or object to store the data and not the state object.
Shallow compare works by checking if two values are equal in case of primitive types like string, numbers and in case of object it just checks the reference. So if you shallow compare a deep nested object it will just check the reference not the values inside that object.
There is also legacy explanation of shallow compare in React:
shallowCompare performs a shallow equality check on the current props and nextProps objects as well as the current state and nextState objects.
It does this by iterating on the keys of the objects being compared and returning true when the values of a key in each object are not strictly equal.
UPD: Current documentation says about shallow compare:
If your React component's render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.
React.PureComponent's shouldComponentUpdate() only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only extend PureComponent when you expect to have simple props and state, or use forceUpdate() when you know deep data structures have changed
UPD2: I think Reconciliation is also important theme for shallow compare understanding.
The accepted answer can be a bit misleading for some people.
user = {
name: "John",
surname: "Doe"
}
const user = this.state.user;
user.name = "Jane";
console.log(user === this.state.user); // true
This statement in particular "Notice you changed users name. Even with this change objects are equal. They references are exactly same."
When you do the following with objects in javascript:
const a = {name: "John"};
const b = a;
Mutating any of the two variables will change both of them because they have the same reference. That's why they will always be equal (==, ===, Object.is()) to each other.
Now for React, the following is the shallow comparison function:
https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js
/**
* Performs equality by iterating through keys on an object and returning false
* when any key has values which are not strictly equal between the arguments.
* Returns true when the values of all keys are strictly equal.
*/
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return
For non-primitives (Objects), it checks:
If the first object is equal (using Object.is()) to the second.
If not, it checks if each key-value pair in the first object is equal (using Object.is()) to that of the second. This is done for the first level of keys. If the object has a key whose value is another object, this function does not check for equality further down the depth of the object.
It took me a while to actually know shallow compare and === are two different thing, especially while reading redux documentation in the following.
However, when an action is dispatched to the Redux store, useSelector() only forces a re-render if the selector result appears to be different than the last result. As of v7.1.0-alpha.5, the default comparison is a strict === reference comparison. This is different than connect(), which uses shallow equality checks on the results of mapState calls to determine if re-rendering is needed. This has several implications on how you should use useSelector().
strict equal
so step by step, the strict equal === is very consistently defined by the Javascript language, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality
What it does is to compare two items by value if they are primitive, and then by reference if they are object. Of course if the types of two objects are different, they will never match.
shallow compare
shallow probably isn't a build-in feature of the language. Couple of the answers here pointed us to some variation of the implementation, https://github.com/facebook/fbjs/blob/main/packages/fbjs/src/core/shallowEqual.js
The idea is to compare two items by value if they are primitive. But for non primitives, we go one level lower. For objects, if the keys are different between two objects, we say they are not same. If the value under a key is different, we say they are not same either.
summary
This means, shallow comparison checks more than the strict equal ===, especially when it comes to the object. A quick look might suggest, === does not do too much guess work.
The shallow equal snippet by #supi above (https://stackoverflow.com/a/51343585/800608) fails if prevObj has a key that newObj doesn't have. Here is an implementation that should take that into account:
const shallowEqual = (objA, objB) => {
if (!objA || !objB) {
return objA === objB
}
return !Boolean(
Object
.keys(Object.assign({}, objA, objB))
.find((key) => objA[key] !== objB[key])
)
}
Note that the above doesn't work in Explorer without polyfills.
There is an implementation with examples.
const isObject = value => typeof value === 'object' && value !== null;
const compareObjects = (A, B) => {
const keysA = Object.keys(A);
const keysB = Object.keys(B);
if (keysA.length !== keysB.length) {
return false;
}
return !keysA.some(key => !B.hasOwnProperty(key) || A[key] !== B[key]);
};
const shallowEqual = (A, B) => {
if (A === B) {
return true;
}
if ([A, B].every(Number.isNaN)) {
return true;
}
if (![A, B].every(isObject)) {
return false;
}
return compareObjects(A, B);
};
const a = { field: 1 };
const b = { field: 2 };
const c = { field: { field: 1 } };
const d = { field: { field: 1 } };
console.log(shallowEqual(1, 1)); // true
console.log(shallowEqual(1, 2)); // false
console.log(shallowEqual(null, null)); // true
console.log(shallowEqual(NaN, NaN)); // true
console.log(shallowEqual([], [])); // true
console.log(shallowEqual([1], [2])); // false
console.log(shallowEqual({}, {})); // true
console.log(shallowEqual({}, a)); // false
console.log(shallowEqual(a, b)); // false
console.log(shallowEqual(a, c)); // false
console.log(shallowEqual(c, d)); // false
Very simple to understand it. first need to understand the pure component and regular component, if a component has coming props or state is changing then it will re-rendered the component again.
if not then not.
in regular component shouldComponentUpdate by default true. and in pure component only the time when state change with diff value.
so now what is shallow component or shallow ?
lets take an simple example.
let a = [1,2,3],
let b = [1,2,3],
a == b ==> shallow take it false,
a == c ==> shallow take it true. c has any diff value.
now i think you can understand it. the diff in both regular and pure component with shallow component
if you like it, also do like share and subscribe my youtube channel
https://www.youtube.com/muosigmaclasses
Thanks.
I feel that none of the answers actually addressed the crucial part in your question, the answers merely explain what shallow comparison is (whether they mean the JavaScript default shallow comparison that is a result of the === or == operator or React's shallowCompare() function)
To answer your question, my understanding so far of React makes me believe that yes indeed by not directly mutating the states then shouldComponentUpdate will always return true thus always causing a re-render no matter what objects we pass in setState even if the objects passed to setState hold the same values stored in the current state
example:
Say I have a React.Component with the current state and function:
this.state = {data: {num: 1}} // current state object
foo() { // something will cause this function to called, thus calling setState
this.setState( {data: {num: 1}} ); // new state object
}
You can see that setState passed the same object (value-wise) however plain React is not smart enough to realize that this component shouldn't update/re-render.
To overcome this, you have to implement your version of shouldComponentUpdate in which you apply deep comparison yourself on the state/props elements that you think should be taken into consideration.
Check out this article on lucybain.com that briefly answers this question.

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

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.

Categories