Check if array of objects has changed - javascript

I have an array that could contain objects. Objects can either be added to it or have a property modified. I want to check if the array has changed at all (could be element(s) added or simply just have one object have a key changed), and then update the DB based on the potential change.
Just wanna know if what I have will cover all cases and/or if there is a better way to do it.
const origArrayCopy = JSON.stringify(origArray);
someFnThatPotentiallyChanges(origArray);
if (origArrayCopy !== JSON.stringify(origArray)) {
updateDB(arr);
} else {
console.log('NO DIFF');
}
And here's a jsFiddle I created to test around with https://jsfiddle.net/j4eqwmp6/
Converting the object to a string using stringify should account for deep-nested changes, right? Any insights on this implementation and is there now a more appropriate way to do it?

Using JSON.stringify is certainly a possibility.
An alternative, is to wrap the object (array) in a proxy, and do that for every nested object as well. Then trap all actions that mutate those objects.
Here is how that could look:
function monitor(obj, cb) {
if (Object(obj) !== obj) return obj;
for (let key of Object.keys(obj)) {
obj[key] = monitor(obj[key], cb);
}
return new Proxy(obj, {
defineProperty(...args) {
cb();
return Reflect.defineProperty(...args);
},
deleteProperty(...args) {
cb();
return Reflect.deleteProperty(...args);
},
set(...args) {
cb();
return Reflect.set(...args);
}
});
};
// Example array
let origArray = [{x: 1}, { child: { y: 1} }];
// Activate the proxy:
let dirty = false;
origArray = monitor(origArray, () => dirty = true);
// Perform a mutation
origArray[1].child.y++;
console.log(dirty); // true
console.log(origArray);

Related

Insert element inside array

I have a function
checkName(output) {
output.filter((NewData) => {
return this.props.elements.filter((OldData) => {
if (NewData.key == OldData.key) {
NewData.name = OldData.name,
//there i need to add another element
// Need to add newData.number = OldData.number
}
return NewData
})
})
return output
}
and I call this function like:
const named = this.checkName(product.rows)
Now I need to add to my product's array that I passed to checkName the value "OldData.Number" to "newData.Number" that is not defined in product (so I need to create this field)
For example:
Product before the checkName function
product.rows = [NewData.name]
Product after the checkName function
product.rows = [NewData.name="value of OldData.name", NewData.number="value of OldData.number"]
How can I obtain this result?
There are 2 confusing things in your code:
You are using filter to execute an action in each member of the output array. However, filter should be used to... well, filter that array, meaning that is should not modify it, just return a sub-set of it. Instead, you might want to use forEach. However, taking into accound the next bullet, probably you want to use map.
You are modifying the array passed to the checkName function. This is confusing and can lead to hard-to-find bugs. Instead, make your function "pure", meaning that it should not mutate its inputs, instead just return the data you need from it.
I would suggest some implementation like this one:
checkName(output){
return output.map((NewData) => {
// find the old data item corresponding to the current NewData
const OldData = this.props.elements.find(x => x.key === NewData.key);
if (OldData) {
// If found, return a clone of the new data with the old data name
// This uses the spread syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
return {
...NewData, // Clone the NewData object
name: OldData.name, // set the value found in OldData.name in the "name" field of the cloned object
number: OldData.number, // You can do the same for each field for which you want to replace the value cloned from NewValue
};
} else {
// Otherwise, just return a clone of the NewData
return { ...NewData };
}
}
}
The usage would be like this:
const named = this.checkName(product.rows)
Be aware that the product.rows array won't be modified!
You can get keys and values of the old object.
const keys = Object.keys(oldObject);
const values = Object.values(oldObject);
// or
const [keys, values] = Object.entries(oldObject);
After, you will create a loop with all keys of oldObject, and insert in newObject like a array.
keys.forEach( (key, index) => newObject[key] = values[index]);
// or
for (const [key, value] of Object.entries(object1)) {
newObject[key] = value
}
Use map like this.
checkName(output){
return output.map(( NewData) =>{
this.props.elements.forEach((OldData) => {
if (NewData.key == OldData.key) {
NewData.name = OldData.name;
NewData.number = OldData.number;
}
})
return NewData;
})
// return output;
}

How add getters to all objects

How can I add getters (or prototype/method) to all object.
I have an object that look like:
foo.bar.text
//or
foo.bar.child.text
text - is an array of strings, but I need only one of them.
Each time when I get this value, I want get only one fixed index (this index saved in other variable).
So what I need in result:
foo.text = ['a','b']
foo.bar.text = ['c','d']
foo.bar.someChild.text = [null, undefined]
x = 1;
// here we make some magic
console.log(foo.text) // b
console.log(foo.bar.text) // d
console.log(foo.bar.someChild.text) // undefined
So if any object contains an array text, if we try get it, we get not array but some defined item from it.
Manually point on item I can't so foo.bar.text[x] is not an option.
Name of array and variable that we get is optional, for example we can save array in fullText and try get text. As if text = fullText[x].
Can somebody advice how I can implement this, getter, setter, prototype?
Update
Proxy seems is my option, thanks for advice!
I would suggest you apply Proxy recursively to the foo object.
// the code is handwriting without test
var handler = {
get: (target, prop) => {
if (prop === 'text') {
if (target[prop] instanceof Array) {
return target[prop][x];
} else {
// dealing with non-array value
}
} else if (typeof target[prop] === 'object') {
return new Proxy(target[prop], handler);
} else {
return target[prop];
}
}
}
var newFoo = new Proxy(foo, handler);

Check if key exist

I have groupedTags and I need to add fields to it and add new tags across field.:
let groupedTags = {
'other': {}
}
if (!groupedTags.other[field]) {
groupedTags.other[field] = [];
}
groupedTags.other[field].push(tag);
I understand that it is necessary to initialize a new field to push new tag - Is there a more beautiful way to check if a field exists every time? I mean avoid explicit check. or is there nothing terrible about this check? there are just a lot of places where it can be repeated
Maybe you should investigate using Proxies for achieving your desired result.
Here's short example of doing so CodeSandbox -example
1) Create proxy handler to customise Object behaviour
const handler = {
set: function(obj, key, value) {
if (!obj[key]) {
obj[key] = [];
}
obj[key].push(value);
return true;
}
};
2) Assign Proxy to your Object
let groupedTags = {
other: new Proxy({}, handler)
};
Now assigning new value will go trough Proxy
groupedTags.other.b = "bar";
// {"other":{"b":["bar"]}}
If you want to create an array of elements on an empty (or not) object, you can try this. So you don't have to check if the property you want to push your element(s) already exists.
If it doesn't it will concat with an empty array, giving you an array with the one element, otherwise the value will be added to that array. Hope this helps.
const o = {};
const toAdd = [1,2,3,4];
toAdd.forEach((v) => {
o.other = (o.other || []).concat(v);
});
o.other2 = (o.other2 || []).concat(123);
console.log(o);
I believe there is no way around checking for a value + if needed initalizing it, other than doing it yourself explicitly.
You can take out one occurrence of groupedTags.other[field] using || like this:
let field = "foo", tag = "bar";
let groupedTags = {
'other': {}
}
// Get the existing items, or assign a new list & use it:
var items = groupedTags.other[field] || (groupedTags.other[field] = []);
items.push(tag);
console.log(groupedTags);
You could also make use of a helper method that encapsulates the check-and-init part:
let groupedTags = {
'other': {}
}
AddTag(groupedTags.other, "foo1", "bar1");
AddTag(groupedTags.other, "foo2", "bar2a");
AddTag(groupedTags.other, "foo2", "bar2b");
console.log(groupedTags);
// Can put this in a library.js
function AddTag(obj, field, tag) {
var items = obj[field] || (obj[field] = []);
items.push(tag);
}

Defining an indexer for an object

One can make an object iterable by implementing [Symbol.iterator].
But how can one override the behavior of the [] operator?
For example i have a an object which has an array inside of it and i want to be able to access that given an index like obj[3].
is that possible?
example
const SignalArray = (data = []) => {
...
return {
add,
remove,
onAdd,
onRemove,
onSort,
removeOnAdd,
removeOnRemove,
removeOnSort,
[Symbol.iterator]() {
return {
next: () => {
if (index < data.length) {
return { value: data[index++], done: false };
} else {
index = 0;
return { done: true };
}
}
}
}
}
}
how can one override the behavior of the [] operator?
Only via Proxy, added in ES2015. You'd provide a get trap and handle the property keys you want to handle.
Here's an example where we check for property names that can be successfully coerced to numbers and return the number * 2:
const o = new Proxy({}, {
get(target, prop, receiver) {
const v = +prop;
if (!isNaN(v)) {
return v * 2;
}
return Reflect.get(...arguments);
}
});
o.x = "ex";
console.log(o[2]); // 4
console.log(o[7]); // 14
console.log(o.x); // "ex"
If you want to override setting the array element, you'd use set trap. There are several other traps available as well. For instance, in a reply to a comment, you said:
...if you hate es6 classes and want to write a wrapper around an array that gives it extra functionality, like an observable array for example...
...that would probably involve a set trap and overriding various mutator methods.

Editing a jagged array in Javascript

I wish to simulate a taskbar (of running tasks/apps). I plan to store tasks something like this:
(function ()
{
var tasks = [];
addTask = function (taskName, taskWindow)
{
if (!tasks[taskName]) { tasks[taskName] = []; }
tasks[taskName].push({ taskWindow: taskWindow, taskName: taskName});
};
removeTask = function (taskName, taskWindow)
{
if (tasks[taskName])
{
//Somehow remove the object from the array
}
};
}());
How should I write removeTask() to remove the correct element from this jagged array?
I suggest using object to store your tasks, because it will make your ( specific to your requirement, I am not talking about Array vs Object) code cleaner and easier to maintain
var taskManager = (function(){
function taskManager(tasks){
// Do your tasks validation before passing to this.
var this.tasks = tasks || {}; // tasks value is not private here
}
// Assuming taskID would be unique value
taskManager.prototype.addTask = function (taskName, taskID){
if ( !this.tasks[taskID] ) {
this.tasks[taskID] = { taskID: taskID, taskName: taskName };
}
};
taskManager.prototype.removeTask = function (taskName, taskID){
if (this.tasks[taskID]){
delete this.tasks[taskID];
}
};
return taskManager;
})();
Usage:
var taskManager1 = new taskManager();
taskManager1.addTask(a,b);
taskManager1.removeTask(a);
Arrays are meant to have numeric indexes and you can use .splice() to remove a numeric indexed item from an array. Non-numeric indexes aren't really in the array, they end up just being properties on the array object and they can be removed with the delete operator.
If you don't have numeric indexes, then you should be using an object and use a property to index each item. When doing it that way, you can use delete tasks[taskName] to remove a property from the object.

Categories

Resources