add value to specific object in array by index of array - javascript

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

Related

Javascript: Recursion returning undefined

Why is my recursion returning undefined? I'm trying to "decode" nested children data from mongo which is returned as IDs like:
{
"_id": "613fd030f374cb62f8f91557",
"children": [
"613fd035f374cb62f8f9155b",
"613fd136f374cb62f8f91564",
"613fd1a5f374cb62f8f91571",
"613fd20bf374cb62f8f9157c"
],
...more data
}
My goal is to drill down and convert each child ID to the Object the ID represensents and convert their child IDs to objects then keep going until the child === [] (no children). I'm trying to have the initial parent (613fd030f374cb62f8f91557) have access to all multi-level nested children objects.
This is my code:
const get_documents = (documents) => {
// Loop through each document
documents.map((document) => {
if (document.parent === null) {
//convert children ids (_id) to array of objects
let dbData = [];
document.children.map((id) => {
let dbChildren = documents.find((x) => x._id === id);
dbData.push(dbChildren);
});
let formattedData = [];
dbData.map((child) => {
let formattedObject = {
id: child._id,
name: child.name,
depth: 0,
parent: child.parent,
closed: true,
children: child_recursion(child.children),
};
formattedData.push(formattedObject)
});
}
});
};
const child_recursion = (arr) => {
let dbData = [];
arr.map((id) => {
let dbChildren = documents.find((x) => x._id === id);
dbData.push(dbChildren);
});
let formattedData = [];
dbData.map((child) => {
let newChild = [];
if (child.children.length > 1) {
newChild = child_recursion(child.children);
}
let formattedObject = {
id: child._id,
name: child.name,
depth: 0,
parent: child.parent,
closed: true,
children: newChild,
};
formattedData.push(formattedObject);
if (newChild === []) {
return formattedData
}
});
};
What am I doing wrong in my recursion? Thank you for the help!
What is getting you here is mixing mutation with recursion which tends to make things a lot more messy.
What this line is telling me:
children: child_recursion(child.children),
is that you are always expecting child_recursion to return an array of formatted children.
However, in child_recursion you aren't always returning something. Sometimes you are mutating sometimes instead. Personally, I believe that it tends to be easier to wrap my head around not using mutation.
The process, therefore, should go something like this:
given an object
check if that object has children
if it does convert the children using this function
if it does not, stop recursion
return a new object, created from the input object with my children set to the output of the conversion.
In this way we can convert each child into an object with its children converted and so on.
Also it is somewhat strange that you are trying to convert all documents at once. Instead, as you gave in your question, you should focus on the object you are trying to convert and work downwards from there. If it is the case where objects can be both parents and children then you have a graph, not a tree and recursion would have to be handled differently than you are expecting.
We don't really need two functions to do this, just one and in the case where you already have the objects you are searching you can pass that along as well (if you don't just remove documents and get them from the db or some service instead). We can also use what is called an accumulator to set initial values before our recursion and track them as we recur.
const convert_children = (obj, documents) => {
const convert_children_acc = (obj, documents, parent, depth) => {
let partial_format = {
id: obj._id,
name: obj.name,
depth: depth,
parent: parent,
close: true
}
if (obj.children && obj.children.length === 0) {
return {
...partial_format,
children: []
}
} else {
return {
...partial_format,
children: obj.children.map(child => {
child = documents.find(x => child === x._id);
return convert_children_acc(child, documents, obj._id, depth+1)
})
}
}
}
return convert_children_acc(obj, documents, null, 0);
};
https://jsfiddle.net/5gaLw1y7/

Modifying one JavaScript array using setState() updates a second state array

My React App displays a grid of names for selection. When I select a name it reads a database and shows rows of data applicable to that name. With each row being an object, which is stored as a single array element.
I have two arrays to contain the objects, one is the 'original data' and the other is the 'modified data'. Then I compare these to see if there has been a change to one of the rows before updating the database.
The arrays are defined so:
constructor(props) {
super(props);
this.state = {
...
detail_data: [],
old_detail_data: []
}
}
When I select a row a call is made to _handleRowClick():
_handleRowClick(i) {
if (i >= 0) {
this.getRecord(i, (err, res) => {
if (!err) {
let detail = res.body;
this.setState({
name_detail: Object.assign({}, detail), // Stores details about the name
selectedIndex: i
})
// Fetch the Career Directions
this.fetchSingleCareerDirections(this.state.detail.animal_code); // Gets the rows
}
})
}
}
fetchSingleCareerDirections(animal_code) {
request.get(`/lookup/career_directions/${animal_code}`, (err, res) => {
let data = [].concat(res.body); // row data
this.setState({
detail_data: [...data], // array 1
old_detail_data: [...data], // array 2
});
});
}
At this point all is well and my data is as expected in detail_data and old_detail_data. So I modify one piece of data in one row, in this case clicking a checkbox (for valid career), but any change to row data has the same effect:
<td>
<input type="checkbox"
checked={item.valid_career == 'Y' ? true : false}
style={{ width: 30 }}
name={"valid_career|" + i}
onChange={(e) => { this._setTableDetail("valid_career", e.target.checked == true ? 'Y' : 'N', i) }}
/>
</td>
Which calls the update routine _setTableDetail() to store a 'Y' or 'N' into the detail_data array:
_setTableDetail(name, value, index) {
let _detail_data = Object.assign([], this.state.detail_data);
_detail_data[index][name] = value;
this.setState({ detail_data: _detail_data });
}
This updates this.state.detail_data as expected. But if I look at this.state.old_detail_data the exact change has also been made to that array. Likewise, as a test, if I modify old_detail_data that updates detail_data.
This MUST be happening because the two arrays both reference the same memory space. But I cannot see how that is happening. My setState() routine, as seen above, does this:
this.setState({
detail_data: [...data],
old_detail_data: [...data],
});
Which, to my understanding, uses the spread operator to create a new array in each instance. So how are these two arrays both referencing the same memory space? Is it something to do with me doing the cloning inside the setState() call maybe?
Thanks very much #Anthony for your comment. You were quite right in where I was going wrong. So although my arrays were unique their contained objects were referencing the same objects in memory.
I modified the code of the fetchSingleCareerDirections() function to resolve the issue:
fetchSingleCareerDirections(animal_code) {
request.get(`/lookup/career_directions/${animal_code}`, (err, res) => {
let data = [].concat(res.body);
this.setState({
detail_data: data.map(row => Object.assign({}, row)), // array 1
old_detail_data: data.map(row => Object.assign({}, row)), // array 2
});
});
}
The program now works perfectly.
Just one point to note. I am using a slightly older version of JavaScript for which the spread operator didn't work (sadly). But testing this elsewhere beforehand I was able to use Anthony's code, as suggested, which is a better more modern approach:
fetchSingleCareerDirections(animal_code) {
request.get(`/lookup/career_directions/${animal_code}`, (err, res) => {
let data = [].concat(res.body);
this.setState({
detail_data: data.map(row => ({ ...row })), // spread operator
old_detail_data: data.map(row => ({ ...row })), // spread operator
});
});
}

Cycle.js - How to get collection length in a collection item

I'm trying to add some behavior exclusively to the last item in a list in Cycle.js. I tried to use cycle-onionify to make a collection like so:
const List = makeCollection({
item: Child,
itemKey: (childState, index) => String(index),
itemScope: key => key,
collectSinks: instances => {
return {
onion: instances.pickMerge('onion'),
DOM: instances.pickCombine('DOM')
.map(itemVNodes => ul(itemVNodes))
}
}
});
I understand that lenses can be used to share state between components, but there doesn't seem to be a way to use lenses with a collection. I'm thinking I could pass the Collection length to the children so I could compare it with an id.
Is there something I am missing?
You can use lenses with makeCollection. Remember it returns a normal Cycle.js component that you can isolate. So if you want to add a boolean isLast you can do this like this:
function omit(obj, key) {
let tmp = { ...obj }; //Copy the object first
delete tmp[key];
return tmp;
}
const listLens = {
get: stateArray => stateArray.slice(0, -1).concat({
...stateArray[stateArray.length - 1],
isLast: true
}),
set: (stateArray, mappedArray) => mappedArray.slice(0, -1)
.concat(omit(mappedArray[mappedArray.length - 1], 'isLast'))
};
const List = isolate(
makeCollection({
item: Child,
itemKey: (childState, index) => String(index),
itemScope: key => key,
collectSinks: instances => ({
onion: instances.pickMerge('onion'),
DOM: instances.pickCombine('DOM')
.map(itemVNodes => ul(itemVNodes))
})
}),
{ onion: listLens, '*': null }
);
As a side note, if you want to apply a lens on each individual item, you can do so too, with the itemScope property. For example
itemScope: key => ({ onion: myLens, '*': key })

Creating an array from a nested dictionary

I'm trying to extract a nested dictionary from a DataSnapshot.
var query = admin.database().ref("meetings").orderByChild("deadline");
query.once("value").then((snapshot) =>
{
snapshot.forEach((child) =>
{
console.log(snapshot.val());
}
}
This code prints this:
{
'82BE0F33-6812-4EFF-8CB4-FABDACA2B329':
{
deadline: 1509634558,
name: 'cfedfgh',
users:
{
'8YUDtUJuIde8fLaHCfeuSNsHUyq2': '1',
DVAYNspxcGfB001RSkq3S8cxvsH3: '1',
fJXgRJBoFAU0SmA2Zhn4DpdyLGh1: '1'
}
}
}
How would I go about creating an array of dictionary users values? Or access them at all.
Doing console.log(child.child("users").val()); gives me a null.
Database structure:
Using DataSnapshot#child() will give you another DataSnapshot for the location at the specified relative path. Therefore, a DataSnapshot with children is considered a parent node so doesn't have a specific value, so val() won't work here.
In order to convert your users value into any array, you'll need to iterate over each child underneath it and pass the key into an array, so something like this:
snapshot.forEach((child) => {
let users = [];
if (child.child("users").exists()) {
child.child("users").forEach((userSnapshot) => {
users.push(userSnapshot.key);
});
}
console.log(users);
}
Assuming snapshot is an array of objects, you can use the following. This will give you all of the users for each record.
const users = [].concat.apply([], snapshot.map(row => {
return Object.keys(row).map(key => {
return row[key].users
})
}))
console.log(users)

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

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}})
, {});

Categories