Object push Firebase, how to remove key names from pushed items - javascript

I have this Object.key code that pushes all items:
const cloned_items = [];
Object.keys(items).sort().map(key => {
let item = {
[`item-${uid}`]: {
item: false
}
}
cloned_items.push({ ...item });
});
database.ref('/app/items').update({
...cloned_items
})
but this produces following result:
"0" : {
"timeslot-87dah2j" : {
item: false
}
},
"1" : {
"timeslot-7s1ahju" : {
item: false
}
}
instead of:
"timeslot-87dah2j" : {
item: false
},
"timeslot-7s1ahju" : {
item: false
}
any idea ?

It seems like you want to create a plain object, not an array.
In that case:
const cloned_items = Object.assign(...Object.keys(items).map(uid =>
({ [`item-${uid}`]: {item: false} })
));
NB: sorting is of no use when creating an object -- its keys are supposed to have no specific order.

You're creating an array of objects. Seems like you want to use .reduce() to create a single object from the array.
const cloned_items = Object.keys(items).sort().reduce((obj, key) =>
Object.assign(obj, { [`item-${uid}`]: { item: false } })
, {});
Your code doesn't show where uid is coming from, but I assume you meant key there, along with timeslot instead of item.
You may find Object.defineProperty to be cleaner, though you'll need to set up the property descriptor as you want it.
const cloned_items = Object.keys(items).sort().reduce((obj, key) =>
Object.defineProperty(obj, `item-${uid}`, {value:{item: false}})
, {});

Related

Why doesn't reassigning the parameter element in forEach work

For the following code block:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
item = {
...item,
...changes
}
})
console.log(items) // items NOT reassigned with changes
items.forEach((item, i) => {
items[i] = {
...item,
...changes
}
});
console.log(items) // items reassigned with changes
Why does reassigning the values right on the element iteration not change the objects in the array?
item = {
...item,
...changes
}
but changing it by accessing it with the index does change the objects in the array?
items2[i] = {
...item,
...changes
}
And what is the best way to update objects in an array? Is items2[i] ideal?
Say no to param reassign!
This is a sort of a fundamental understanding of higher level languages like JavaScript.
Function parameters are temporary containers of a given value.
Hence any "reassigning" will not change the original value.
For example look at the example below.
let importantObject = {
hello: "world"
}
// We are just reassigning the function parameter
function tryUpdateObjectByParamReassign(parameter) {
parameter = {
...parameter,
updated: "object"
}
}
tryUpdateObjectByParamReassign(importantObject)
console.log("When tryUpdateObjectByParamReassign the object is not updated");
console.log(importantObject);
As you can see when you re-assign a parameter the original value will not be touched. There is even a nice Lint rule since this is a heavily bug prone area.
Mutation will work here, but ....
However if you "mutate" the variable this will work.
let importantObject = {
hello: "world"
}
// When we mutate the returned object since we are mutating the object the updates will be shown
function tryUpdateObjectByObjectMutation(parameter) {
parameter["updated"] = "object"
}
tryUpdateObjectByObjectMutation(importantObject)
console.log("When tryUpdateObjectByObjectMutation the object is updated");
console.log(importantObject);
So coming back to your code snippet. In a foreach loop what happens is a "function call" per each array item where the array item is passed in as a parameter. So similar to above what will work here is as mutation.
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
// Object assign just copies an object into another object
Object.assign(item, changes);
})
console.log(items)
But, it's better to avoid mutation!
It's better not mutate since this can lead to even more bugs. A better approach would be to use map and get a brand new collection of objects.
const items = [{
id: 1,
name: 'one'
},
{
id: 2,
name: 'two'
},
];
const changes = {
name: 'hello'
}
const updatedItems = items.map((item, i) => {
return {
...item,
...changes
}
})
console.log({
items
})
console.log({
updatedItems
})
As the MDN page for forEach says:
forEach() executes the callbackFn function once for each array
element; unlike map() or reduce() it always returns the value
undefined and is not chainable. The typical use case is to execute
side effects at the end of a chain.
Have a look here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
This means that although you did create new object for item, it was not returned as a value for that index of array. Unlike your second example, the first one is not changing original array, but just creates new objects and returns undefined. This is why your array is not modified.
I'd go with a classic Object.assign for this:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach( (item) => Object.assign(item,changes) )
console.log(items)
Properties in the target object are overwritten by properties in the sources if they have the same key. Later sources' properties overwrite earlier ones.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
The other approach you can take is to use map and create a new array based on the original data and the changes:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
const newItems = items.map((item) => {
...item,
...changes
})
console.log(newItems);
But if you need to modify the original array, it's either accessing the elements by index, or Object.assign. Attempting to assign the value directly using the = operator doesn't work because the item argument is passed to the callback by value not by reference - you're not updating the object the array is pointing at.

javascript string comparison issue in array.filter()

I have an array which contains following objects.
myArray = [
{ item: { id: 111557 } },
{ item2: { id: 500600 } }]
and I have a variable
targetItemID = '111557'
Note that one is string, and the ones in array are numbers. I'm trying to get the object having the correct item id.
Here is what I have tried,
myArray = [
{ item: { id: 111557 } },
{ item2: { id: 500600 } }]
targetItemID = '111557'
var newArray = myArray.filter(x => {
console.log(x.item.id.toString())
console.log(targetItemID.toString())
x.item.id.toString() === itemID.toString()
})
console.log(newArray);
I expect all matching objects to be added to 'newArray'. I tried to check the values before comparison, They are both strings, they seem exactly same, but my newArray is still empty.
Your second object doesn't have an item property and should.
You need a return in your filter function.
You must compare x.item.id against targetItemID, not itemID. Since you are using console.log() you would have seen and error of itemID id not defined ;).
myArray = [
{ item: { id: 111557 } },
{ item: { id: 500600 } }
];
targetItemID = '111557'
var newArray = myArray.filter(x => {
//console.log(x.item.id.toString())
//console.log(targetItemID.toString())
return x.item.id.toString() === targetItemID.toString();
});
console.log(newArray);
There are a few issues here. First, not all your objects have an item property, so you'll need to check it exists. Second, you're comparing them against a non-existent itemID instead of targetItemID, and finally, and #bryan60 mentioned, if you open a block in an anonymous lambda, you need an explicit return statement, although, to be honest, you really don't need the block in this case:
var newArray =
myArray.filter(x => x.item && x.item.id && x.item.id.toString() === targetItemID)
you need to return for filter to work:
return x.item.id.toString() === itemID.toString();

add value to specific object in array by index of array

How to add value to a specific object to the array by the index?
I wrote this, but of course, it creates a new object in the array, but I want to insert "errors" to an existing object with index (on screen it 0 index)
ipcRenderer.on('fileData', (event, data) => {
this.setState({jobs: [...this.state.jobs, {errors: data}]})
});
Then i wrote this:
ipcRenderer.on('fileData', (event, data) => {
this.state.jobs.forEach((item, index) => {
this.setState({jobs: [...this.state.jobs, {errors: item[index] = data}]
})
console.log(this.state)
})
});
It inserts a value into the object, but without a name and it still creates a new element in the array
I want the result to be like this:
jobs: [
0: {errors: 10, fileName:...}
]
If you know the index, you can just do
const jobs = this.state.jobs.slice(0);
jobs[index].errors = data;
this.setState({jobs});
Might have to do more than slice the array, might have to make a deep copy, but yeah, that should work.
Firstly you can make a copy of your array like
let jobsCopy = this.state.jobs
Then if you know the index you could just do like
jobsCopy[index].errors = 10
this.setState({
jobs: jobsCopy
})
You would need to know the index of the object you want to change. For example if you know it is the first item in the array you can do this:
const indexToChange = 0
this.setState(prevState => prevState.map((obj, i) => {
if(i === indexToChange) {
return {
...obj,
errors: data
}
} else {
return obj
}
}))

Vue JS run computed property for each key value of another computed property

I have a computed property:
relateditems () {
return this.related.find((relation) => {
return relation.id === this.currentItem.id
})
},
with the following output:
relateditems:Object
-KQ1hiTWoqAU77hiKcBZ:true
-KQ1tTqLrtUvGnBTsL-M:true
id:"-KQ1if2Zv3R9FdkJz_1_"
I'm trying to create another computed property that then loops through the relateditems object and finds a relation with the matching ID for the first two keys.
The following doesn't work but I think it gives the idea:
relateditemsdata () {
let test = []
for (var key in this.relateditems) {
this.items.find((relateditem) => {
relateditem.id === key.id
test.push(relateditem)
})
}
return test
}
I think calling a computed property in another one is not a good way, so you could add a watcher property in order to watch the first computed property and update a data object property based on that one like :
data() {
return {
relateditemsdata: [],
}
},
computed: {
relateditems() {
return this.related.find(relation => {
return relation.id === this.currentItem.id
})
},
},
watch: {
relateditems(val) {
for (var key in this.relateditems) {
this.items.find(relateditem => {
relateditem.id === key.id
relateditemsdata.push(relateditem)
})
}
},
},
Seems like your problem is not related to Vue.
Your key.id is undefined in the for..in loop. You would have to use this.relateditems[key] to access the value or just key to access the key. Since you do not want filter your other array for the 'id' key, you should also filter your object-keys first.
E.g.
relatedItems() {
return this.related.find((item) => {
return this.item.id == this.currentItem.id;
});
} // returns first objects with the same id
relatedItemsData() {
// grabs all keys except 'id'
const keys = Object.keys(this.relateditems).filter((key) => key != 'id');
this.items.filter((item) => {
return keys.indexOf(item.id) != -1; // checks if item.id is inside the keys-array
});
} // returns again array of objects;
Instead of a nested loop, you can also just use the Array.prototype.filter() function, like above.

How to set value of an immutable state in Javascript?

Given an immutable state like this:
alerts: {
5a8c76171bbb57b2950000c4: [
{
_id:5af7c8652552070000000064
device_id:5a8c76171bbb57b2950000c4
count: 1
},
{
_id:5af7c8722552070000000068
device_id:5a8c76171bbb57b2950000c4
count: 2
}
]
}
and an object like this:
{
_id:5af7c8652552070000000064
device_id:5a8c76171bbb57b2950000c4
count: 2
}
I want to replace the object with the same id in the alerts state (immutable), such that end result looks like this:
alerts: {
5a12356ws13tch: [
{
_id:5af7c8652552070000000064
device_id:5a8c76171bbb57b2950000c4
count: 2
},
{
_id:5af7c8722552070000000068
device_id:5a8c76171bbb57b2950000c4
count: 2
}
]
}
How can I do that? With mergeDeep, getIn, setIn, and updateIn, found on List, Map or OrderedMap ?
I tried doing something like this.. where index is 0 and deviceId is 5a12356ws13tch
Does not work though.
export const oneAlertFetched = (state, {deviceId, index, alert}) => state.setIn(['alerts', deviceId, index], alert).merge({fetching: false})
I tried this as well. Does not work.
export const oneAlertFetched = (state, {deviceId, index, alert}) => {
const a = state.alerts[deviceId][index]
state.alerts[deviceId][index] = Object.assign({}, a, alert)
return
}
By immutable, you mean that your property is non-writable.
If you want to modify your object in-place (not recommended), you will need the property to be at least configurable:
const device = alerts['5a12356ws13tch'][0];
if (Object.getOwnPropertyDescriptor(device, 'count').configurable) {
// Manually make it `writable`
Object.defineProperty(device, 'count', {
writable: true
});
// Update property's value
device.count++;
// Set it back to `non-writable`
Object.defineProperty(device, 'count', {
writable: false
});
}
console.log(device.count); // 2
If it is not configurable (cannot make it writable), or you do not want to jeopardize your application (it must be non-writable on purpose), then you should work on copies.
const device = alerts['5a12356ws13tch'][0];
alerts['5a12356ws13tch'][0] = Object.assign({}, device, {count: device.count + 1});
Object.assign() works on flat objects. If you need deep copy, have a look at my SO answer there.
I think you mean you want to return a new object with the updated payload?
function getNextAlerts(alerts, parentDeviceId, payload) {
const alertsForDevice = alerts[parentDeviceId];
if (!alertsForDevice || alertsForDevice.length === 0) {
console.log('No alerts for device', deviceId);
return;
}
return {
...alerts,
[parentDeviceId]: alerts[parentDeviceId].map(item =>
item._id === payload._id ? payload : item
),
}
}
const alerts = {
'5a12356ws13tch': [
{
_id: '5af7c8652552070000000064',
device_id: '5a8c76171bbb57b2950000c4',
count: 1
},
{
_id: '5af7c8722552070000000068',
device_id: '5a8c76171bbb57b2950000c4',
count: 2
}
]
};
const nextAlerts = getNextAlerts(alerts, '5a12356ws13tch', {
_id: '5af7c8652552070000000064',
device_id: '5a8c76171bbb57b2950000c4',
count: 2,
});
console.log('nextAlerts:', nextAlerts);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
If you're working with plain JavaScript objects and want to keep "immutable" approach you have to use spreads all over the nested structure of state object.
But, there are some tools already targeting this issue - lenses.
Here is the example of both approaches, array/object spreads and lenses - ramda repl.
In short, your example via spreads:
const oneAlertFetched = (state, { deviceId, index, alert }) => ({
...state,
alerts: {
...state.alerts,
[deviceId]: [
...state.alerts[deviceId].slice(0, index),
{ ...state.alerts[deviceId][index], ...alert },
...state.alerts[deviceId].slice(index + 1)
],
}
})
And via lenses using Ramda's over, lensPath, merge and __*:
const oneAlertFetched = (state, { deviceId, index, alert }) =>
R.over(
R.lensPath(['alerts', deviceId, index]),
R.merge(R.__, alert),
state
)
* R.__ placeholder used to swap 1st & 2nd parameters of R.merge
PS: lenses solution is intentionally adjusted to match the declaration of your function, so you can easily compare two approaches. However, in real life, with such powerful and flexible tool, we can rewrite the function to be more readable, reusable, and performant.

Categories