How to add and update objects into array of objects? - javascript

I am trying to dynamically add an object to an array of objects, I have been trying to Destructuring the main object but it adds a number to the end of the parent array. This is what I have:
const [data, setData] = useState ([
{
_id:1,
firstName:'Leo',
lastName:'Miller',
telephone:'+569273829',
mail:'leo.miller#gmail.com',
work:[
{_id:1, startWorkDate:'01/01/2015', endWorkDate:'01/02/2017', work:'description...'},
{_id:2, startWorkDate:'01/01/2018', endWorkDate:'01/02/2020', work:'description...'}
]
}];
I generate dynamically this object:
const value = {_id:3, startWorkDate:'01/01/2018', endWorkDate:'01/02/2020', work:'description...'}
I need to add it into data.work and after that update only the description of work._id[3]
I try with this function
const addNewWork = (value) => {
let copyData = [...data, data[0].workExperience.push(value)]
return setData(copyData)
}
but for some reason doesn't add correctly the object. Help please!

You have an array and not an object. Your statement
let copyData = [...data, data[0].workExperience.push(value)]
is doing two things:
mutating the state by doing push(). Which is not the react way.
creating a new array. Also adding a new item to the array, but that is the new length of data[0].workExperience.
The return value of Array.prototoype.push is:
The new length property of the object upon which the method was called.
What you have to do is:
Make a copy of the array. Can use ... (spread operator) here.
Make a copy of the array object you want (first index). Try to add the object to its specific property workExperience.
const addNewWork = (value) => {
let newData = [...data];
let newWorkExperienceArray =
[...data[0].workExperience,value];
let newDataFirstObject = {...data[0], workExperience : newWorkExperienceArray};
newData[0] = newDataFirstObject;
return setData(newData)
}
You can also update the property. I didn't find the relevant code in your question as to what I have to update so I didn't update anything in the third workExperience object.
EDIT: It seems in your code the property name is work and not workExperience. Please confirm. The above code uses workExperience, you can replace it by work if that is the case

You can do this with this simple function:
const addNewWork = (value) => {
let updatedObj = data[0];
updatedObj.work.push(value)
// updates your object each time
let copyData = [updatedObj]
// adds a new object for each call
// let copyData = [...data, updatedObj]
return setData(copyData)
}
Now it updates the object in your state. If you want to add a new object for each call just uncomment let copyData = [...data, updatedObj] and comment out let copyData = [updatedObj]

When you set state for array your setter is a quite bite different
setData(prevData => [...prevData, newItem]) // to add a single item to array
setData(prevData => newArray) // to replace entire array

Related

Need to keep original array values and create copy, change one value in copy but original is changed also?

I'm using react/redux to fetch data etc.
const dates = useSelector((state) => state.holidays.holidays);
const [newDates, setNewDates] = useState(null);
useEffect(() => {
dispatch(getAllHolidays());
}, []);
useEffect(() => {
if (dates) {
setNewDates([...dates]);
}
}, [dates]);
So I'm using the "useSelector" to get the data from my Redux/state component.
Then i'm using a "useState" to keep a COPY of the dates in there.
The first "useEffect" will get the data I need on component mount/start.
The second "useEffect" will fill in the COPY of dates, every time dates changes (in state).
Anyway, whenever I change "newDates" value now, and do a console.log of the "dates" array, everything's changed as well... I thought using the spreader operator would create a new array (so no previous reference to the "dates"-array, and then fill it with all objects of the "dates"-array. But seemingly it's not the case?
For example, here's how I use it:
const handleDateDescriptionChange = (id, event) => {
let newDatesArray = [...newDates];
newDatesArray.find(date => date.id === id).description = event.target.value;
console.log(dates.find(x => x.id === id).description);
setNewDates([...newDatesArray]);
}
const deleteDateChanges = (id) => {
newDates.find(x => x.id === id).description = dates.find(x => x.id === id).description;
console.log(newDates.find(x => x.id === id).description);
console.log(dates.find(x => x.id === id).description);
setNewDates([...newDates]);
}
In the first function, I want to change the value of the object (with corresponding ID) to set it's value of it's "description" to whatever the user typed in in a TextField.
So just to be sure, I create a new array copy (no reference to previous) locally, then find the object I need (based on id), then change it's "description" value.
Then I console.log the original "dates" array, and it's modified too!!
In the second function I'm trying to reset the modified "description" value to the original value from the "dates"-array, but it's not possible because the "dates"-array was changed also and the values are the same, so the second function does nothing now, functionally.
Am I missing something here?!
This is because an array is stored in the heap. Arrays are reference types, therefore if you try to assign an array to a new array, the new array won't be a copy. It will reference to the array storage in the heap.
On of the easiest way to real copy an array is to loop through the array and assign every value by itself to the new array. Therefore the new array get it's own heap storage place but just with the same values as the original one.
Here is a good explanation if you are more interested in this topic:
Link
Shallow copying only works are at the root level of the operation, all more deeply nested objects are still references back to the originals. You must shallow copy all nested state that is being updated to avoid state mutations.
const handleDateDescriptionChange = (id, event) => {
// use array.map to shallow copy the array
setNewDates(dates => dates.map(date => date.id === id ? {
...date, // <-- shallow copy date if id match
description: event.target.value
} : date)); // <-- otherwise return original since no update
}
const deleteDateChanges = (id) => {
// use array.filter to shallow copy array and remove elements
setNewDates(dates => dates.filter(date => date.id !== id));
}

Fetch pokemon API forEach issue

I need help with the forEach method. I am trying to mess around with the Pokemon API, but I can't run a forEach method without it returning undefined.
When I tried to use forEach instead of the map (I noticed map returned one big array) it still returned undefined.
fetch('https://pokeapi.co/api/v2/pokemon?limit=151')
.then(res => res.json())
.then(data => fetchPokemon(data))
const fetchPokemon = (res) => {
console.log(res)
// This turns the object api into an array
const arr = [res];
console.log(arr)
// This creates a variable to map correctly
const firstArr = arr[0].results
// This creates a new array with pokemon name
const array = firstArr.map(pokemon => pokemon.name)
console.log(array);
const html =
`
<div>${array}</div>
`
const pokemon = document.querySelector('.pokemon');
pokemon.innerHTML = html;
}
So there's a few things going on:
You don't need ".then(res => res.json())" because the response is already in json. You'll also want to extract the data property specifically like:
axios('https://pokeapi.co/api/v2/pokemon?limit=151')
.then(({data}) => fetchPokemon(data))
"const arr = [res];" does not turn the object into an array but rather places it into an array. Also, there's a further result layer you need to extra, so you'd want to instead do:
const fetchPokemon = (res) => {
//extract the results property into its own variable
const {results} = res;
//create an array of just object.name properties
const names = results.map( pokemon => pokemon.name)
}
Finally you can use the map property to create an array of just the pokemon names.
The forEach() method calls a function once for each element in an array, in order.
forEach: This iterates over a list and applies some operation with
side effects to each list member (example: saving every list item to
the database)
map: This iterates over a list, transforms each member of that list,
and returns another list of the same size with the transformed members
(example: transforming list of strings to uppercase)
from here
Meaning, if you want to do something for every element of the array, foreach is the correct function.
Example of Array.ForEach
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
Now, you are trying to create a new array with all the pokemon names, therefore map is correct - you are mapping the array into a new array.
However, if you want to, say, make an li element in html for every name, then it would be correct to use ForEach.
Example:
fetch('https://pokeapi.co/api/v2/pokemon?limit=151')
.then(res => res.json())
.then(data => fetchPokemon(data))
const fetchPokemon = (res) => {
const firstArr = [res][0].results
var ul = document.getElementById("pokemon");
firstArr.forEach(function (entry) {
var li = document.createElement('li');
li.appendChild(document.createTextNode(entry.name));
ul.appendChild(li);
});
}
<ul id="pokemon"></ul>
The forEach method is supposed to return undefined.

React hooks useState/setState not working after sorting the array and passing it

I have React app and I am using React hooks:
const [companies, setCompanies] = useState([]);
I am fetching the data and 'companies' is getting filled with data. I also have another button for sorting the data by NetIncome:
const sortByIncome = e => {
const el = document.getElementById("sort-selectbox");
const arr = companies.sort((a, b) => (a.NetIncome > b.NetIncome ? -1 : 1));
console.log(arr);
setCompanies(arr);
};
The problem is that setCompanies does not re-renders. In the console.log I can see that array is sorted correctly and even if I console.log(companies) I can see that it is also sorted. But noting happens in the interface. Also if I type the same code:
const sortByIncome = e => {
const el = document.getElementById("sort-selectbox");
const arr = companies.sort((a, b) => (a.NetIncome > b.NetIncome ? -1 : 1));
console.log(arr);
setCompanies([]);
};
but pass to setCompanies empty array it immediately works and displays nothing (I have render function that gets companies as param).
So why it is not working with passing arr? Is it because I am passing the same array but just sorted?
Here:
const arr = companies.sort((a, b) => //...
Array.prototype.sort sorts array in place, that is, mutates the original object. The reference doesn't change, and the mutation doesn't get noticed. Do instead
const arr = [...companies].sort((a, b) => //...
The value of the hook-variable companies inside the function sortByIncome will always be the initial value of your hook ([]) and will never receive the new value set by using setCompanies.
A solution to your problem is to wrap your function sortByIncome in a useCallback hook with the hook-variable companies as a dependency. This will ensure that the function always uses the current value of companies.
const sortByIncome = React.useCallback(e => {
// Using `companies` will always result in the current value
}, [companies]);
useCallback documentation

How to update object property which is an array?

I am working in a ReactJS project and have a filterGroupsData property in my state. This is an array of objects, each object has a filters property which is an array of string values. See below:
filterGroupsData:[
{"key":1532957059388,"id":1532957059388,"filters":[]},
{"key":1532957059612,"id":1532957059612,"filters":[]},
{"key":1532957059847,"id":1532957059847,"filters":[]}
]
How can I add elements to the filters property of a object with a given id?
I attempted to this but this results in overwriting the whole object with on value:
// update the filter array of the object with id == activeId
let updatedFilterGroupsData = filterGroupsData.map(filterGroupData => filterGroupData.id === activeId ? filterGroupData.filters.push('test') : filterGroupData)
this.setState({filterGroupsData: updatedFilterGroupsData});
Appreciate any help.
You can use findIndex to get the index of the filter group you want to update, and then create a copy of the object and the filters array and add a new entry to it.
Example
const id = 1532957059388;
this.setState(previousState => {
const filterGroupsData = [...previousState.filterGroupsData];
const index = filterGroupsData.findIndex(group => group.id === id);
filterGroupsData[index] = {
...filterGroupsData[index],
filters: [...filterGroupsData[index].filters, "new filter"]
};
return { filterGroupsData };
});
Here: filterGroupData.filters = 'test'
You're setting the prop value to a string instead of putting in the array.
You need to push the item into the filters array like:
filterGroupData.filters.push('test');
filters is a array so you need to use push('test') with multi line code inside map:
var filterGroupsData = [
{"key":1532957059388,"id":1532957059388,"filters":[]},
{"key":1532957059612,"id":1532957059612,"filters":[]},
{"key":1532957059847,"id":1532957059847,"filters":[]}
]
var activeId = 1532957059612;
let updatedFilterGroupsData = filterGroupsData.map((filterGroupData) => {
if(filterGroupData.id === activeId) {
filterGroupData.filters.push('test');
}
return filterGroupData
});
console.log(updatedFilterGroupsData);

Why does a js map on an array modify the original array?

I'm quite confused by the behavior of map().
I have an array of objects like this:
const products = [{
...,
'productType' = 'premium',
...
}, ...]
And I'm passing this array to a function that should return the same array but with all product made free:
[{
...,
'productType' = 'free',
...
}, ...]
The function is:
const freeProduct = function(products){
return products.map(x => x.productType = "free")
}
Which returns the following array:
["free", "free", ...]
So I rewrote my function to be:
const freeProduct = function(products){
return products.map(x => {x.productType = "free"; return x})
}
Which returns the array as intended.
BUT ! And that's the moment where I loose my mind, in both cases my original products array is modified.
Documentation around map() says that it shouldn't ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map ).
I even tried to create a clone of my array turning my function into this:
const freeProduct = function(products){
p = products.splice()
return p.map(x => {x.productType = "free"; return x})
}
But I still get the same result (which starts to drive me crazy).
I would be very thankful to anyone who can explain me what I'm doing wrong!
Thank you.
You're not modifying your original array. You're modifying the objects in the array. If you want to avoid mutating the objects in your array, you can use Object.assign to create a new object with the original's properties plus any changes you need:
const freeProduct = function(products) {
return products.map(x => {
return Object.assign({}, x, {
productType: "free"
});
});
};
2018 Edit:
In most browsers you can now use the object spread syntax instead of Object.assign to accomplish this:
const freeProduct = function(products) {
return products.map(x => {
return {
...x,
productType: "free"
};
});
};
To elaborate on SimpleJ's answer - if you were to === the two arrays, you would find that they would not be equal (not same address in memory) confirming that the mapped array is in fact a new array. The issue is that you're returning a new array, that is full of references to the SAME objects in the original array (it's not returning new object literals, it's returning references to the same object). So you need to be creating new objects that are copies of the old objects - ie, w/ the Object.assign example given by SimpleJ.
Unfortunately, whether the spread operator nor the object assign operator does a deep copy.... You need to use a lodash like function to get areal copy not just a reference copy.
const util = require('util');
const print = (...val) => {
console.log(util.inspect(val, false, null, false /* enable colors */));
};
const _ = require('lodash');
const obj1 = {foo:{bar:[{foo:3}]}};
const obj2 = {foo:{bar:[{foo:3}]}};
const array = [obj1, obj2];
const objAssignCopy = x => { return Object.assign({}, x, {})};
const spreadCopy = x => { return {...x}};
const _Copy = x => _.cloneDeep(x);
const map1 = array.map(objAssignCopy);
const map2 = array.map(spreadCopy);
const map3 = array.map(_Copy);
print('map1', map1);
print('map2', map2);
print('map3', map3);
obj2.foo.bar[0].foo = "foobar";
print('map1 after manipulation of obj2', map1); // value changed
print('map2 after manipulation of obj2', map2); // value changed
print('map3 after manipulation of obj2', map3); // value hasn't changed!
Array Iterator Array.map() creates the new array with the same number of elements or does not change the original array. There might be the problem with referencing if there is object inside the array as it copies the same reference, so, when you are making any changes on the property of the object it will change the original value of the element which holds the same reference.
The solution would be to copy the object, well, array.Splice() and [...array](spread Operator) would not help in this case, you can use JavaScript Utility library like Loadash or just use below mention code:
const newList = JSON.parse(JSON.stringify(orinalArr))
Array Destructuring assignment can be used to clone the object.
const freeProduct = function(products){
p = products.splice()
return p.map(({...x}) => {x.productType = "free"; return x})
}
This method will not modify the original object.

Categories

Resources