Destructuring fallback to prevent undefined error? - javascript

I have a list of array I do this:
const { id } = myArray.find(
(obj) => obj === true)
If the id is not present it will throw error. How to prevent error in the same time use destructuring? I want to keep the logic in one line.

The issue here is .find() returns undefined once there is no fulfillment for the condition:
The value of the first element in the array that satisfies the provided testing function. Otherwise, undefined is returned.
So probably you can use || operator to check if you have any value returned from .find() or you can replace with empty object {} instead on the right side of the operator.
Probably the option for one-liner is the following:
const myArray = [
{ id: 12 },
{ id: 13 },
{ id: 14 }
];
const { id } = myArray.find(e => e.id === 17) || {};
console.log(id);
So you can destructure id property even if it returned undefined in this way.
Also if you need you can add default value to your destructuring statement based on the documentation which states for Destructuring statement as follows:
A variable can be assigned a default, in the case that the value unpacked from the object is undefined.
const { id = 10 } = {};
console.log(id);
I hope this helps!

Related

Conditionally add properties to an object if referencing variable contains string

Some state
const [submittedSev, setSubmittedSev] = useState('');
const [newSev, setNewSev] = useState('');
Mixpanel events where I'm sending properties that may or may not exist. Sometimes the submittedSev and newSev may be empty strings but this will still send
mixpanel.track('COMPLETED', {
response: props.details.title,
submittedSev,
newSev
});
I'd like to only add submittedSev and newSev properties if the string isn't empty. For sure I could set up a conditional statement and check the string length and send a different Mixpanel event but that doesn't seem succinct enough.
Output of data that sometimes gets sent
{
"response": "hello",
"submittedSev": "",
"newSev": ""
}
How can I only add properties to object if they are not empty strings?
Here's a really concise way to do it using the spread ... operator and evaluating an expression with the AND operator && which returns the second value if the first is truthy:
// Example.js
const name = ''
const email = 'asd#gmail.com'
const phone = ''
const output = {
...(name && {name}),
...(email && {email}),
...(phone && {phone})
}
console.log(output)
So for your code it'd be:
mixpanel.track('COMPLETED', {
response: props.details.title,
...(submittedSev && {submittedSev}),
...(newSev && {newSev})
});
You could centralize the logic that sends the events in a separate function, and go through the properties of the event object and filter out the ones that are not empty strings:
function trackMixpanelEvent(eventType, event) {
const keys = Object.keys(event).filter(
key => Object.prototype.hasOwnProperty.call(event, key));
keys.forEach(key => {
if (event[key] === '') {
delete event[key];
}
});
mixpanel.track(eventType, event);
}
And then just call it without worrying which properties are empty strings:
trackMixpanelEvent('COMPLETED', {
response: props.details.title,
submittedSev,
newSev
});

Trying to see if object key exists keep getting undefined

I have a data response that responds with different objects in an array.
I have tried a variety of different methods to determine if the key 'name' exists including:
const hasName = array.forEach(item => "name" in item )
const hasName = array.forEach(item => Object.keys(item).includes('name') )
const hasName = array[0].hasOwnProperty("name") ? true : null
const hasName = array => array.some(x => x.some(({ name }) => name));
// lodash 'has' method
const hasName = has(array, 'name')
Array1 returns objects like:
[
{
name: 'cool name'
}
]
Array2 returns objects like:
[
{
group: 'cool group'
}
]
All of the methods I tried have either returned undefined or nothing at all and I'm completely at a loss for why. I have scoured Stack Overflow and the internet trying to get a better direction or an answer but I haven't been able to find anything.
You're not returning anything from some of your calls on the array. forEach for example runs a callback, and always returns undefined see docs. Your code just isn't working because you're using the functions incorrectly.
The code below filters your array to get the elements with a name property, then counts them to see whether one or more exists, which results in a true or false being stored in the hasName variable.
let myArr = [
{
name: 'cool name'
}
]
const hasName =
myArr
.filter(a => a.hasOwnProperty("name")) // filter for elements in the array which have a name property
.length // get the number of filtered elements
> 0 // check whether the number of elements in array with name prop is more than 0
console.log(hasName)
If you are sure that the "response" is fully received before the check, THEN
Your latest variant of the check can be implemented as follows:
array.some(obj => (obj.name !== undefined)); // fast, but not define .name if it is "undefined"
array.some(obj => obj.hasOwnProperty("name")); // slower, but will define .name with any value
Your solutions are generally correct, but it looks like you're a little confused,
Array.forEach always returns "undefined":
array.forEach(obj => {
console.log("name" in obj); // true for 1st array
console.log("group" in obj); // true for 2nd array
}); // return "undefined"
array[0].hasOwnProperty() works fine, and can't return "undefined" at all.
console.log(
array[0].hasOwnProperty("name") ? true : null; // return ("true" for the 1st array) AND ("null" for the 2nd)
);
When you used the Lodash, maybe you forgot to point to the object index?
_.has(array[0], 'name'); // return ("true" for the 1st array) AND ("false" for the 2nd)
Try a for loop
for (var i = 0; i < array.length; i++) {
if (array[i].hasOwnProperty('name')) {
console.log(array[i].name); //do something here.
break;
}
}

cannot set property of undefined in a loop

I'm doing a loop to generate array of object. I have used faker to generate fake name, but in specified iteration I want to insert my own value, in this case the person's car.
Below code I got cannot set property of car of undefined, what's wrong?
const person = []
times(2, index => {
if (index === 0) {
person[0].car = 'honda'
} else if(index ===1) {
person[1].car = 'ford'
}
person.push({
name: faker.random.name()
})
}
console.log(person)
Apart from some closing parentheses missing, you try to first access an unexisting object (person[0].car...), and create it later (person.push(...)).
Reverse the order.
let newPerson = { name: faker.random.name(), car: 'dodge' };
person.push(newPerson);
person is an empty array initially, there are no objects in it, so when trying to do person[0].car it throws error because person[0] is undefined and and you cannot set or get any property from undefined.
Instead do this
person[0] = {}
person.car = 'honda'
or
person[0] = {'car': 'honda'}

javascript object hash vs lodash foreach in lodash remove

I'm trying to delete some data from a complex object.
The format of the original data is as follows.
let originData =
[
{
name : exampleDepth1,
depth1Data :
[
{
name : exampleDepth2,
depth2Data :
[
{
code: 1234///will be delete from that data
},
...
]
},
...
]
},
....
]
let willbeDeletecode = [ 3, 100, 1234, 1000];
The name of the array to be deleted is the code value in the depth2Data array of originData, The name of the array to delete is willbeDeletecode.
I'm sorry if you were uncomfortable.
I'm trying to erase it in two ways.
let deleteBook = {}
_.forEach(willbeDeletecode, (deleteCode) => {
deleteBook[`${deleteCode}`] = deleteCode;
})
_.remove(originData, (depth1) => {
_.remove(depth1.depth1Data, (depth2) => {
/*
// delete with object hash
_.remove(depth2.depth2Data, (eachDepth2Data) => {
return deleteBook[eachDepth2Data.code] === undefined
})
*/
/*
// delete with forEach
let ret = false;
_.remove(depth2.depth2Data, (eachDepth2Data) => {
_.forEach(willbeDeletecode, (deleteCode) => {
if(deleteCode === eachDepth2Data.code){
ret = true;
return false;
}
})
return ret
})
*/
return depth2.depth2Data.length === 0;
})
return depth1.depth1Data.length === 0;
})
I have two separate ways of annotating each one.
The first is to create an object(deleteBook) and insert the data of willbeDeletecode and use it in remove of lodash.
The second method is entirely a comparison of all through the forEach function.
The above method was repeated 1000 times to benchmark. As a result, the first method is 100 ~ 200ms and the second method is 500 ~ 700ms.
Of course, the willbeDeletecode is around 10 or so, but I thought Object hash was faster. But the result was the opposite.
If there are more variables in willbeDeletecode, will there be another conclusion? I want know why this results.
The object hash is to be preferred. You could also use an ES6 Set for such purpose.
Such a hash solution should be faster.
One reason that you did not see this in your case, is that the first variant of your code removes the opposite of what it should. The _remove callback should return a truthy value when the corresponding item should be removed, yet your code returns true when the value is not in the codes that should be deleted. You should use a !== comparison:
_.remove(depth2.depth2Data, (eachDepth2Data) => {
return deleteBook[eachDepth2Data.code] !== undefined
})
As you had a === there, you probably had a lot more removals going on, giving a longer time of execution.

Design pattern to check if a JavaScript object has changed

I get from the server a list of objects
[{name:'test01', age:10},{name:'test02', age:20},{name:'test03', age:30}]
I load them into html controls for the user to edit.
Then there is a button to bulk save the entire list back to the database.
Instead of sending the whole list I only want to send the subset of objects that were changed.
It can be any number of items in the array. I want to do something similar to frameworks like Angular that mark an object property like "pristine" when no change has been done to it. Then use that flag to only post to the server the items that are not "pristine", the ones that were modified.
Here is a function down below that will return an array/object of changed objects when supplied with an old array/object of objects and a new array of objects:
// intended to compare objects of identical shape; ideally static.
//
// any top-level key with a primitive value which exists in `previous` but not
// in `current` returns `undefined` while vice versa yields a diff.
//
// in general, the input type determines the output type. that is if `previous`
// and `current` are objects then an object is returned. if arrays then an array
// is returned, etc.
const getChanges = (previous, current) => {
if (isPrimitive(previous) && isPrimitive(current)) {
if (previous === current) {
return "";
}
return current;
}
if (isObject(previous) && isObject(current)) {
const diff = getChanges(Object.entries(previous), Object.entries(current));
return diff.reduce((merged, [key, value]) => {
return {
...merged,
[key]: value
}
}, {});
}
const changes = [];
if (JSON.stringify(previous) === JSON.stringify(current)) {
return changes;
}
for (let i = 0; i < current.length; i++) {
const item = current[i];
if (JSON.stringify(item) !== JSON.stringify(previous[i])) {
changes.push(item);
}
}
return changes;
};
For Example:
const arr1 = [1, 2, 3, 4]
const arr2 = [4, 4, 2, 4]
console.log(getChanges(arr1, arr2)) // [4,4,2]
const obj1 = {
foo: "bar",
baz: [
1, 2, 3
],
qux: {
hello: "world"
},
bingo: "name-o",
}
const obj2 = {
foo: "barx",
baz: [
1, 2, 3, 4
],
qux: {
hello: null
},
bingo: "name-o",
}
console.log(getChanges(obj1.foo, obj2.foo)) // barx
console.log(getChanges(obj1.bingo, obj2.bingo)) // ""
console.log(getChanges(obj1.baz, obj2.baz)) // [4]
console.log(getChanges(obj1, obj2)) // {foo:'barx',baz:[1,2,3,4],qux:{hello:null}}
const obj3 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 30 }]
const obj4 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 20 }]
console.log(getChanges(obj3, obj4)) // [{name:'test03', age:20}]
Utility functions used:
// not required for this example but aid readability of the main function
const typeOf = o => Object.prototype.toString.call(o);
const isObject = o => o !== null && !Array.isArray(o) && typeOf(o).split(" ")[1].slice(0, -1) === "Object";
const isPrimitive = o => {
switch (typeof o) {
case "object": {
return false;
}
case "function": {
return false;
}
default: {
return true;
}
}
};
You would simply have to export the full list of edited values client side, compare it with the old list, and then send the list of changes off to the server.
Hope this helps!
Here are a few ideas.
Use a framework. You spoke of Angular.
Use Proxies, though Internet Explorer has no support for it.
Instead of using classic properties, maybe use Object.defineProperty's set/get to achieve some kind of change tracking.
Use getter/setting functions to store data instead of properties: getName() and setName() for example. Though this the older way of doing what defineProperty now does.
Whenever you bind your data to your form elements, set a special property that indicates if the property has changed. Something like __hasChanged. Set to true if any property on the object changes.
The old school bruteforce way: keep your original list of data that came from the server, deep copy it into another list, bind your form controls to the new list, then when the user clicks submit, compare the objects in the original list to the objects in the new list, plucking out the changed ones as you go. Probably the easiest, but not necessarily the cleanest.
A different take on #6: Attach a special property to each object that always returns the original version of the object:
var myData = [{name: "Larry", age: 47}];
var dataWithCopyOfSelf = myData.map(function(data) {
Object.assign({}, data, { original: data });
});
// now bind your form to dataWithCopyOfSelf.
Of course, this solution assumes a few things: (1) that your objects are flat and simple since Object.assign() doesn't deep copy, (2) that your original data set will never be changed, and (3) that nothing ever touches the contents of original.
There are a multitude of solutions out there.
With ES6 we can use Proxy
to accomplish this task: intercept an Object write, and mark it as dirty.
Proxy allows to create a handler Object that can trap, manipulate, and than forward changes to the original target Object, basically allowing to reconfigure its behavior.
The trap we're going to adopt to intercept Object writes is the handler set().
At this point we can add a non-enumerable property flag like i.e: _isDirty using Object.defineProperty() to mark our Object as modified, dirty.
When using traps (in our case the handler's set()) no changes are applied nor reflected to the Objects, therefore we need to forward the argument values to the target Object using Reflect.set().
Finally, to retrieve the modified objects, filter() the Array with our proxy Objects in search of those having its own Property "_isDirty".
// From server:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Mirror data from server to observable Proxies:
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
Object.defineProperty(ob, "_isDirty", {value: true}); // Flag
return Reflect.set(...arguments); // Forward trapped args to ob
}
}));
// From now on, use proxied data. Let's change some values:
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
// Collect modified data
const dataMod = data.filter(ob => ob.hasOwnProperty("_isDirty"));
// Test what we're about to send back to server:
console.log(JSON.stringify(dataMod, null, 2));
Without using .defineProperty()
If for some reason you don't feel comfortable into tapping into the original object adding extra properties as flags, you could instead populate immediately
the dataMod (array with modified Objects) with references:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Prepare array to hold references to the modified Objects
const dataMod = [];
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
if (dataMod.indexOf(ob) < 0) dataMod.push(ob); // Push reference
return Reflect.set(...arguments);
}
}));
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
console.log(JSON.stringify(dataMod, null, 2));
Can I Use - Proxy (IE)
Proxy - handler.set()
Global Objects - Reflect
Reflect.set()
Object.defineProperty()
Object.hasOwnProperty()
Without having to get fancy with prototype properties you could simply store them in another array whenever your form control element detects a change
Something along the lines of:
var modified = [];
data.forEach(function(item){
var domNode = // whatever you use to match data to form control element
domNode.addEventListener('input',function(){
if(modified.indexOf(item) === -1){
modified.push(item);
}
});
});
Then send the modified array to server when it's time to save
Why not use Ember.js observable properties ? You can use the Ember.observer function to get and set changes in your data.
Ember.Object.extend({
valueObserver: Ember.observer('value', function(sender, key, value, rev) {
// Executes whenever the "value" property changes
// See the addObserver method for more information about the callback arguments
})
});
The Ember.object actually does a lot of heavy lifting for you.
Once you define your object, add an observer like so:
object.addObserver('propertyKey', targetObject, targetAction)
My idea is to sort object keys and convert object to be string to compare:
// use this function to sort keys, and save key=>value in an array
function objectSerilize(obj) {
let keys = Object.keys(obj)
let results = []
keys.sort((a, b) => a > b ? -1 : a < b ? 1 : 0)
keys.forEach(key => {
let value = obj[key]
if (typeof value === 'object') {
value = objectSerilize(value)
}
results.push({
key,
value,
})
})
return results
}
// use this function to compare
function compareObject(a, b) {
let aStr = JSON.stringify(objectSerilize(a))
let bStr = JSON.stringify(objectSerilize(b))
return aStr === bStr
}
This is what I think up.
It would be cleanest, I’d think to have the object emit an event when a property is added or removed or modified.
A simplistic implementation could involve an array with the object keys; whenever a setter or heck the constructor returns this, it first calls a static function returning a promise; resolving: map with changed values in the array: things added, things removed, or neither. So one could get(‘changed’) or so forth; returning an array.
Similarly every setter can emit an event with arguments for initial value and new value.
Assuming classes are used, you could easily have a static method in a parent generic class that can be called through its constructor and so really you could simplify most of this by passing the object either to itself, or to the parent through super(checkMeProperty).

Categories

Resources