cannot set property of undefined in a loop - javascript

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

Related

Iterate through a specific nested object key

There is an object like:
props = {
any: 'thing',
intro: { content: 'foo' }
}
Now I want to loop through a given string representing a specific path (props.intro.content) in a way to set the deepest value to undefined:
props.intro.content = undefined
props.intro = undefined
props = undefined
So the results by iterating the path given above should output these three objects:
{
any: 'thing',
intro: { content: undefined }
},
{
any: 'thing',
intro: undefined
},
undefined
I tried to use split and for loop
const array = 'props.intro.content'.split('.')
for (let index = array.length - 1; index > -1; index--) {
console.log([array.join('.')]) // this returns the flatten path
array.pop()
}
but this doesn't handle the object itself, so I do not get the correct object as output.
Here's a recursive function that does that. I made the example extra deep to show that it's robust to deep nesting.
The one tricky part is the last one you should get just undefined if the path starts with the variables name. You can't get the name of the variable referencing the object you pass into the function, so maybe you can add a boolean parameter that pushes undefined to the end of the array, and have the string input start at the first key layer.
const object = {
any: 'thing',
intro: {
content: {
lets: {
go: {
deeper: 20
}
}
}
}
}
function deepDelete (mainObj, locations) {
const props = locations.split('.')
const outputs = [];
function recurseDelete(obj, props, mainObj) {
if (props.length === 0) return
recurseDelete(obj[props[0]], props.slice(1), mainObj)
obj[props[0]] = undefined
outputs.push(structuredClone(mainObj))
}
recurseDelete(mainObj, props, mainObj);
return outputs
}
const locations = 'intro.content.lets.go.deeper';
const outputArray = deepDelete(object, locations)
console.log(outputArray)

How to get the opposite property?

I have the following object with (always) 2 properties. It wil always have 2 properties. 3 properties won't be possible:
var people = {
'John': { ... },
'Peter': { ... }
}
And I have the variable var name = 'John'.
Is there a simple way to get the value of property 'Peter' when the value of name is 'John'?
Without hard-coding it with the names John en Peter. So the functionality must get the opposite property of the value in variable name
let name = 'John'; // or whatever
let names = Object.keys(people);
let otherName = names.find(n => n !== name);
people[otherName] // this gives you the value of the other name's property
Object.keys will give you an array of the property names.
filter lets you filter that array.
So:
const name = "John";
const people = {
'John': 1,
'Peter': 1
};
const [result] = Object.keys(people).filter(person => person !== name);
console.log({
result
});
I wrote a simple function, that does this. It takes a key, and an object. It returns the value of the element in the given object by the inverse key of the given key. This will only work if the object only has two keys.
var people = {
'John': { 'b':[3,4] },
'Peter': { 'a':[1,2] }
}
getInverseObjectElem = (key,object) => { // Define function 'getInverseObjectElem'
listOfObjectKeys = Object.keys(object) // Create an array of the object keys
inverseKey = listOfObjectKeys.filter(k => k != key)[0] // filter the list of keys, and get the first key that doesnt match the given key.
return object[inverseKey] // Return the value in the object, by the inverseKey
}
console.log(getInverseObjectElem('John',people))
You could try this:
for (let key in people){
if (key != name) return people[key];
}

Destructuring fallback to prevent undefined error?

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!

Why isn't forEach function letting me returning an object or even just passing it out from the function?

I'm trying do make a simple app which shows and save your favorites (let's call them easily "object"). I thought to do it like:
get the favorite from vuex,
find the favorite in the array of possible favorites,
manage my favorite as I want.
name value is correct, I already know vuex state.
Elenco is an array got by a JSON, it works because I use it in a v-for and in other two functions (none uses forEach function).
the if statement works good otherwise it won't show the first "i".
let name = this.$store.state.favTime.id;
console.log(name);
Elenco.forEach(element => {
if(element.name === name){
var i = element;
console.log(i); //element
}
});
console.log(i); //undefined
I would like just to access to that object so that i can manipulate it but i can't because it's undefined.
(Hope to have followed correctly these guidelines and hope my english is enough goood to make it understandable, thanks).
If you need to access that variable after the for loop, declare it before the loop:
var i;
Elenco.forEach(element => {
if(element.name === name){
i = element;
console.log(i); //element
}
});
console.log(i);
you might want to use .find()
let name = this.$store.state.favTime.id;
let elem = Elenco.find(element => {
return element.name === name
});
console.log(element);
The find() method returns the value of the first element in the array
that satisfies the provided testing function. Otherwise undefined is
returned.
if you want to find the last element, you can reverse the array :
let name = this.$store.state.favTime.id;
let elem = [...Elenco].reverse().find(element => {
return element.name === name
});
console.log(element);
I suggest you use find instead of forEach for your use case, if available.
const name = this.$store.state.favTime.id;
const favourite = Elenco.find(element => element.name === name)
console.log(favourite)
This is an issue of local and global scope of variables. Variable created inside for loops have local reference, so declare them globally.
var name = "Johnny";
var Elenco = [
{
name: "Jack Torrance",
age: 50,
eyeColor: "blue"
},
{
name: "Toni",
age: 8,
eyeColor: "blue"
},
{
name: "Johnny",
age: 50,
eyeColor: "blue"
}
];
var i = new Array();
Elenco.forEach(element => {
if(element.name === name){
i.push(element);
console.log(i); //element
}
});
console.log(i);

Update Javascript Object, nested Data - insert only if not updateable

Let's say i have the following data
var obj = {
test: 'somedata',
scores: [
{
"points":99,
"id":"x12"
},
{
"points":21,
"id":"x13"
}
],
sites: [
{
"exercises":21,
"sid":"s12"
},
{
"exercises":23,
"sid":"s11"
}
],
history: {
key: 'value',
commits: [
{
id: 1,
value: 'thank you'
}
 ]
}
}
Notice that scores and sites contain arrays with unique elements based on id in scores and based on sid in sites. I want a function that does the following magic:
//will **update** obj.test to 'newdata' and return {test:'newdata'}
magicUpdate(obj, {test:'newdata'})
//will **insert** obj.newkey with with value 'value' and return {newkey: 'value'}
magicUpdate(obj, {newkey: 'value'})
//will do nothing and return {}
magicUpdate(obj, {scores: []})
//will **update** scores[0] and return {scores:[{points:3, id: "x12"}]}, as id "x12" is already in the array at index 0
magicUpdate(obj, {scores:[{points:3, id: "x12"}])
//will **insert** {points:3, id: "x14"} into obj.scores and return {scores:[{points:3, id: "x14"}]}
magicUpdate(obj, {scores:[{points:3, id: "x14"}]})
//will **update** sites[0] and return {sites:[{exercises:22, sid: "s12"}]}, as id "s12" is already in the array at index 0
magicUpdate(obj, {sites:[{exercises:22, sid: "s12"}])
//will **insert** {exercises:10, sid: "s14"} into obj.sites and return {sites:[{exercises:10, sid: "s14"}]}
magicUpdate(obj, {sites:[{exercises:10, sid: "s14"}]})
//and also be recursive ...
//will **update** obj.history.commits[0]
magicUpdate(obj, {'history.commits': [{id:1, value: 'changed'}]});
I have seen .update doing the recursion, but only if one is passing the path which should be determined automatically. Then there is .merge which internally uses _.baseMerge and comes really close to what i need though I do not understand the signature of the function.
_.merge(
{scores:[{id: 12, points:10}, {id: 13, points:10}]},
{scores:[{id: 14, points:10}, {id: 15, points:10}]}
)
// returns {scores:[{id: 14, points:10}, {id: 15, points:10}]} not the fully merged array
Can someone point me to a good direction or has achieved similar things with lodash?
The magicUpdate function you mention in your post could be achieved using lodash functions indeed.
For this implementation, I've used mostly _ .get, _ .set and _ .unionWith though I'm sure it could have been achieved using some others:
// src will be mutated. For simplicity's sake, obj is an object with only one property that represents the changes to make to src
function magicUpdate(src, obj) {
var key = _.first(_.keys(obj)),
value = _.get(obj, key),
srcValue = _.get(src, key),
comparator = function(a, b) {
var idKey = _.isUndefined(a.id) ? 'sid' : 'id';
return a[idKey] === b[idKey];
}
if (_.isArray(srcValue)) {
value = _.unionWith(value, srcValue, comparator);
}
return _.set(src, key, value);
}
As you may have noticed looking at the code, the return type is the mutated object and not what you're asking. I wasn't really sure what you wanted as a return value.
Anyway, Lodash doesn't have a built-in object difference function so it'd be necessary to develop something like that in case you wanted the difference between the old object and the modified one (you'd also have to _ .clone the object first to have a copy and be able to compare).
The idea of the function I present is to try to get the key of obj (it's the key we want to modify in src) and check if it exists and is an array. If so, we just add the two arrays, updating those in src that have the same id in obj. Due to the fact that sites, scores and history had id and sid I had to add some more logic to the comparator of the _.unionWith function.
If key doesn't exist or isn't an array, we just set it in src.
Here you have the fiddle in case you want to play with it. Hope it helps.
UPDATE
My first solution was intended for one property updated at a time. However, it seems that is possible to update more than one at the same time.
One quick solution could be to iterate over the object with the updates and update one property at a time.
function updateProperty(src, obj) {
var key = _.first(_.keys(obj)),
value = _.get(obj, key),
srcValue = _.get(src, key),
comparator = function(a, b) {
var idKey = _.isUndefined(a.id) ? 'sid' : 'id';
return a[idKey] === b[idKey];
}
if (_.isArray(srcValue)) {
value = _.unionWith(value, srcValue, comparator);
}
return _.set(src, key, value);
}
function magicUpdate(obj, src) {
_.forEach(src, function(value, key) {
updateProperty(obj, _.pick(src, key));
});
return obj;
}
Fiddle
I wrote a solution which is recursive and quite performant. See this fiddle.
function mergeRecursive(obj1, obj2) {
if (obj1.constructor == Array) {
for (var i = 0; i < obj1.length; i++) {
if (obj1[i].id == obj2.id) {
obj1[i] = obj2;
return obj1;
}
}
obj1.push(obj2);
return obj1;
}
for (var p in obj2) {
// Property in destination object set; update its value.
if (obj2[p].constructor == Array) {
obj2[p].forEach(function(arrayElement) {
obj1[p] = MergeRecursive(obj1[p], arrayElement);
});
} else if (obj2[p].constructor == Object) {
obj1[p] = MergeRecursive(obj1[p], obj2[p]);
} else {
obj1[p] = obj2[p];
}
}
return obj1;
}

Categories

Resources