This code is assigning the value of 'temps' to this.messages for some reason that I do not understand, any help would be appreciated. I know the issue lies within the below chunk of code as removing the .sort section changes the output order of this.messages.
this.currentMessageSubject.subscribe(()=> {
console.log('Refreshing messages')
if (this.conversationId) {
firebase.firestore().collectionGroup('conv').where("parentMessageId", "==", this.conversationId).get().then(async (querySnapshot) => {
let temps: FirebaseMessages;
let tempsMsgs = new Map();
temps = this.messages;
temps.messages = []
querySnapshot.forEach((newConvo) => {
const thing = { id: newConvo.id, ...newConvo.data() } as FirebaseMessage;
if (!tempsMsgs.has(thing.id)) {
tempsMsgs.set(thing.id, thing)
temps.messages.push(thing);
}
})
temps.messages.sort(function(a,b){
return a.timestamp.seconds - b.timestamp.seconds;
});
temps.messages = temps.messages.filter((message) => {
if (message.id) {
return true;
}
return false;
})
})
}
})
If you assign a non-primitive value to a variable (i.e. object), it will be passed by reference.
So, in your case this line:
temps = this.messages;
assigns the same object that is in your this.messages to your temps variable. So, editing temps will actually edit the this.messages as well, since both are pointing to the same object.
You will have to clone your object, and assign the clone to your temps variable. Regarding cloning - there are multiple good answers about cloning object on SO, so I will not cover that here since we don't know the structure of the messages.
When doing temps = this.messages;, the reference is passed to temps.
You need to clone this object in another way (notice the change line 7) :
this.currentMessageSubject.subscribe(()=> {
console.log('Refreshing messages')
if (this.conversationId) {
firebase.firestore().collectionGroup('conv').where("parentMessageId", "==", this.conversationId).get().then(async (querySnapshot) => {
let temps: FirebaseMessages;
let tempsMsgs = new Map();
temps = {...this.messages};
temps.messages = []
querySnapshot.forEach((newConvo) => {
const thing = { id: newConvo.id, ...newConvo.data() } as FirebaseMessage;
if (!tempsMsgs.has(thing.id)) {
tempsMsgs.set(thing.id, thing)
temps.messages.push(thing);
}
})
temps.messages.sort(function(a,b){
return a.timestamp.seconds - b.timestamp.seconds;
});
temps.messages = temps.messages.filter((message) => {
if (message.id) {
return true;
}
return false;
})
})
}
})
You can refer to this topic to learn more about this : What is the most efficient way to deep clone an object in JavaScript?
Related
I am trying to loop through an argument that is passed to a method and I am getting a TypeError: individualExpenses.map is not a function. What am I doing wrong here?
class ExpenseTracker {
constructor(payCheck, monthlyExpenses) {
this.payCheck = payCheck;
this.monthlyExpenses = monthlyExpenses;
}
storeExpenses(individualExpenses) {
let expenseStore = [];
individualExpenses.map(expense => {
expenseStore.push(expense)
})
console.log(expenseStore)
}
}
const v = new ExpenseTracker({}, {});
v.storeExpenses(1)
You are passing a numerical value to storeExpenses function and applying map over it. map works only on arrays. If you do
v.storeExpenses([1]);
it'll work just fine.
Alternatively, you can build logic to convert a non-array type to an array and use it in your storeExpenses function. This way you can do either of v.storeExpenses(1) or v.storeExpenses([1]) and the function will still work.
e.g.
const wrapToArray = (obj) => {
if (!obj) return [];
return Array.isArray(obj) ? obj : [obj];
};
and then modify your storeExpenses method as below -
storeExpenses(individualExpenses) {
let expenseStore = [];
wrapToArray(individualExpenses).map(expense => {
expenseStore.push(expense)
})
console.log(expenseStore)
}
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;
}
I want to add a side effect every when an array being pushed. For example, I want to add console.log:
var arr = [];
arr.push(1); // => I want it to work normally, and in addition, it logs 1 to the console
How to achieve that? I'm looking for a solution using Proxy and I have tried handler.get() and handler.apply() but still, can't figure it out.
To directly answer your initial question...you need to return a closure from the get trap. To actually trap this, you would need to use proxy.push() instead of array.push(), though. For example:
const arr = [];
const arrProxy = new Proxy(arr, {
get(target, prop) {
if (prop === 'push') {
return (...args) => {
console.log(...args);
return target[prop](...args);
};
}
return target[prop];
}
});
arrProxy.push('test');
arrProxy.push('test1', 'test2');
Here's the final answer that I'm comfortable with, it doesn't use Proxy by the way.
{
var arr = [];
// add push
arr.push = function (...items) {
console.log(...items);
Array.prototype.push.apply(this, items);
};
arr.push('test');
arr.push('test1');
// clean up the push
delete arr.push;
}
something like that ?
Object.defineProperty(Array.prototype, 'myPush',
{
value : function (...val)
{
console.log(...val)
return this.push(...val)
}
})
let aa = []
aa.myPush( 5,123)
console.log('aa = ', aa )
I have been asked to refactor the following code:
const temp = {};
this.sessionData = [];
sessionsData.forEach(session => {
const date = moment(session.startDatetime).format('DDMMYYYY');
if (temp[date]) {
temp[date].push(session);
} else {
temp[date] = [session];
}
});
Apparently it can be more efficient using reduce?
I have tried to simply place reduce in the function but this isnt good enough ;)
const temp = {};
this.sessionData = [];
sessionsData.reduce(session => {
const date = moment(session.startDatetime).format('DDMMYYYY');
if (temp[date]) {
temp[date].push(session);
} else {
temp[date] = [session];
}
});
I understand reduce can add the elements together and have other fun stuff happen during the process BUT specifically I have been asked to sort of get rid of my variables and use them within reduce I suppose!
Any help would be greatly appreciated! Thanks!
If you want to refactor this using reduce, you have to set an inital value {} for the accumulator, and then you have to modify this accumulator, this is what reduce will return after the iterations on your data. You then have to assign this returned accumulator to your temp variable.
So the only input you give to reduce is sessionData, on which reduce is applied. Then at each iteration, it gives you the session and you modify accum.
Also, make sure to return this accum at the end of each iteration, since it has to be passed to the next iteration.
Here is the MDN doc on reduce.
const temp = sessionData.reduce((accum, session) => {
const date = moment(session.startDatetime).format('DDMMYYYY');
if (accum[date]) {
accum[date].push(session);
} else {
accum[date] = [session];
}
return accum;
}, {});
Here you go
let dates = [{startDatetime: '2016-01-02'}, {startDatetime: '2016-02-02'}, {startDatetime: '2016-03-02'}];
const temp = dates.reduce(function(acc, cur) {
const date = moment(cur.startDatetime).format('DDMMYYYY');
if(acc[date]) acc[date].push(cur)
else acc[date] = [cur]
return acc;
}, {})
console.log(temp)
You could use in es6
var sessionData = [
{foo:'bar', startDatetime: Date.now()},
{xxx:'yyy', startDatetime: Date.now()}
];
console.log(
sessionData.reduce((obj,next) => {
const key = moment(sessionData.startDatetime).format('DDMMYYYY');
if(!obj[key]) obj[key] = [];
obj[key].push(next);
return obj;
},{})
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
Hey I'm trying to remove a key:value pair from state inside a Javascript Object.
It works when I hardcode the key name in the code, but when I try to use a variable from a function call, it does nothing.
Can somebody help me out?
Here's an object example:
toppingsSelected: {
"Onion":"true",
"Mushrooms":"true",
}
This works, hardcoded:
deleteTopping = toppingName => {
const { Onion, ...withoutOnion } = toppingsSelected;
console.log(withoutOnion); // Returns object without onion
};
This doesn't work:
deleteTopping = toppingName => {
const toppingName = "Onion"; // Variable gets passed in
const { toppingName, ...withoutOnion } = toppingsSelected;
console.log(withoutOnion); // Returns original object, no change made
};
So I'm basically trying to remove a key from React state but I'm pretty new to Javascript.
How can I make Javascript aware that toppingName is a key?
Another option is to add square brackets arround toppingName, and assign it to a variable. As #Bergi pointed out in the comments, this option does not mutate toppingsSelected
const toppingsSelected = {
"Onion":"true",
"Mushrooms":"true",
};
const toppingName = "Onion";
const {
[toppingName]: topping,
...withoutOnion
} = toppingsSelected;
console.log(JSON.stringify(withoutOnion));
To set the React state, you'd then do this
this.setState({ toppingsSelected: withoutOnion })
You can use delete e.g.
delete toppingsSelected[toppingName];
One way of doing this is using Array.prototype.filter()
const _obj = {
'Onion': true,
'notOnion': false
};
const newObj = Object.keys(_obj)
.filter(key => key !== 'Onion')
.reduce((acc, cur) => ({ ...acc, cur }), {})
console.log(newObj); // { notOnion: false }
This will return a new object without the 'Onion' property