set state to update value in array of object - javascript

this is my code
const [state, setState] = useState(
[{id: 1, key:""}, {id: 2, key:""}, {id: 3, key:""}]
)
i want to to change "key" state
im confuse
now im using
setState(
[...state].map((data, index) => {
if (data.id === state[index].id) {
return {
...data,
key: result,
};
} else return data;
}),
);
}
result variable came from result when i fetching data.
result is a random string

If your data structure is always going to be in that order data.id === state[index].id doesn't really achieve much.
For example:
when data.id is 1 the index will be 0. And state[0].id is 1.
when data.id is 2 the index will be 2. And state[1].id is 2.
etc.
It just sounds like you want to iterate over all the objects in state and update each key value with that random string you mentioned in the comment section. There's no need to make a copy of state since map already returns a new array ready for setState to use.
function setState(mapped) {
console.log(mapped);
}
const state = [{ id: 1, key: '' }, { id: 2, key: '' }, { id: 3, key: '' }];
const result = 'random';
const mapped = state.map(data => {
return { ...data, key: result };
});
setState(mapped);

Related

How do I update a specific key in an array of keys using React

I've got a counter array, containing 3 objects.
const [counter, setCounter] = useState([
{ id: 0, count: [] },
{ id: 1, count: [] },
{ id: 2, count: [] }
])
Theres then 3 buttons that when pressed call the following function.
const update = (i, text) => {
setCounter(currCount =>
currCount.id === i
? { id: i, count: [...counter[i].count, text] }
: currCount
);
};
The buttons pass "i" which is 0,1,2 corresponding to the 3 object ids and "text" which is the error text.
The function should update the specific object from the array adding the new error text to that id's count array.
I cant seem to get this to work though, it keeps returning undefined.
Any help is appreiciated.
The useState dispatch function (setCounter in your case) replaces the whole state with the value it is provided with.
In your example, you need to recreate the whole array like so:
const update = (i, text) => {
setCounter(currCounter =>
[
...currCounter.filter(count => count.id !== i), // the old counter state without the one we want to change
{ id: i, count: [...currCounter[i].count, text] }
]
);
};

How to index an array with map method consisting of objects and arrays

I have an array that is made from another array with the map method in JavaScript:
response = initialResponse.data.Resurs.map((item)=>({
KomRes:item.Kom,
levels:
[
...item.NumList.map((item)=>(
{
KomRes:item.Number,
})),
...item.SerList.map((item,index3)=>({
KomRes:"Serial: " + item.Ser,
})),
]}));
So, I have an array of 1 object and one array of objects. Now, I want to add indexes so that the parent object and all of its child objects have different indexes. One example would be:
[
{
KomRes:"abc"
id:1 // ==> Here the id is different to the levels objects id-s
levels:[{KomRes:"cde",id:2},{KomRes:"cdef",id:3}]
},
{
KomRes:"dfr"
id:4 // ==> Here the id is different to the levels objects id-s
levels:[{KomRes:"dsf",id:5},{KomRes:"sgsd",id:6}]
},
{
KomRes:"fgr"
id:7 // ==> Here the id is different to the levels objects id-s
levels:[{KomRes:"zizu",id:8},{KomRes:"hkl",id:9}]
},
]
As you can see, all of the objects have different ids (indexes). How can I achieve that?
I tried to add index to map method, but don't know how to achieve that with child map methods:
response = initialResponse.data.Resurs.map((item,index)=>({
KomRes:item.Kom,
id:index,
levels:
[
...item.NumList.map((item)=>(
{
KomRes:item.Number,
})),
...item.SerList.map((item,index3)=>({
KomRes:"Serial: " + item.Ser,
})),
]}));
Define a counter variable outside the function then on each iteration each object is given id property with an incremented value of the counter variable. Should there be any sub-arrays, they will be handled recursively by calling itself and passing in the sub-array.
const data=[{KomRes:"abc",id:null,levels:[{KomRes:"cde",id:null},{KomRes:"cdef",id:null}]},{KomRes:"ghi",id:null,levels:[{KomRes:"ijk",id:null},{KomRes:"ijkl",id:null}]},{KomRes:"mno",id:null,levels:[{KomRes:"omn",id:null},{KomRes:"omnp",id:null}]}];
let idx = 1;
function flatIndex(array) {
return array.map(obj => {
if (!obj.id) {
obj.id = idx++;
}
Object.values(obj).map(v => {
if (Array.isArray(v)) {
return flatIndex(v);
}
return obj;
});
return obj;
});
}
console.log(flatIndex(data));
Not sure if I understand well what you want to achieve, but you can declare a variable out of the scope and increment it along.
This gives the result you expect
const response = [
{ Kom: 'abc', NumList: [{ Number: "cde"}], SerList: [{ Ser: "cdef" }] },
{ Kom: 'dfr', NumList: [{ Number: "dsf"}], SerList: [{ Ser: "sgsd"}] },
{ Kom: 'fgr', NumList: [{ Number: "zizu"}], SerList: [{ Ser: "hkl"}] }
];
let lastId = 1; // index var to increment
const result = response.map((item) => ({
KomRes: item.Kom,
id: lastId++,
levels: [
...item.NumList.map((item) => ({
id: lastId++,
KomRes: item.Number,
})
),
...item.SerList.map((item) => ({
id: lastId++,
KomRes: "Serial: " + item.Ser,
})
),
]
})
);
console.log(result)

How to instantly update react state after replacing the value from array

I have a function which gets the user input and update the array if the same key is available already. with the help of this article
This is how it looks;
const handleClickYes = (question) => {
// find if question is in the data array
const hasQuestion = data.find(({ parentId }) => question.id === parentId)
const indexOfQuestion = data.indexOf(hasQuestion)
if (hasQuestion) {
// update the value with specific selected index in the array.
data[indexOfQuestion] = { question: question.if_yes, parentId: question.id, userChoice: 'YES', child: [] }
} else {
setData((data) => data.concat({ question: question.if_yes, parentId: question.id, userChoice: 'YES', child: [] }))
}
localStorage.setItem('deviceReport', JSON.stringify(data))
}
I'm using localStorage to persist the state
const deviceReport = localStorage.getItem('deviceReport') ? JSON.parse(localStorage.getItem('deviceReport')) : []
const [data, setData] = useState(deviceReport)
Here the problem is if I use setData then it updates instantly but on replacing the array
at this part
data[indexOfQuestion] = { question: question.if_yes, parentId: question.id, userChoice: 'YES', child: [] }
it dosent update the mapped data on JSX portion. How can I configure it to update it happen in setState. ? Or any other better option to update the array.
You're not calling setState() within the first half of your if block. Also, never mutate state directly. Make a mutable copy, like so:
const handleClickYes = (question) => {
// find if question is in the data array
const hasQuestion = data.find(({ parentId }) => question.id === parentId);
const indexOfQuestion = data.indexOf(hasQuestion);
// copy data to mutable object
let newData = [...data];
if (hasQuestion) {
// update the value with specific selected index in the array.
newData[indexOfQuestion] = {
question: question.if_yes,
parentId: question.id,
userChoice: "YES",
child: [],
};
} else {
// concat existing data with a new question
newData = [
...newData,
{
question: question.if_yes,
parentId: question.id,
userChoice: "YES",
child: [],
},
];
}
localStorage.setItem("deviceReport", JSON.stringify(newData));
setData(newData);
};

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.

Checking and displaying values of array compared to another array in React

One data set is an object of arrays of ids and another is an object of arrays of ids and names. What I'd like to do is check if the ids from the first data exist in the second data set and if they do then display the names.
This is what is being called by the component, which works correctly:
<td>Genre</td>
<td>{this.matchGenres(this.props.movie.genre_ids, this.props.genres)}</td>
And this is the function that I can't get to work:
matchGenres = (genres, genreList) => {
genres.forEach((genre) => {
genreList.filter((list) => {
return list.id === genre;
}).map((newList) => {
return newList.name;
});
});
}
It looks like the operation performs correctly and returns the right names when I console.log it! But! its not showing up in the component on render.
const genres = [{
id: 1,
name: "Jazz Music"
}, {
id: 2,
name: "Something"
}];
const genreList = [1, 10, 100];
matchGenres = (genres, genreList) => genres
.filter(genre => genreList.includes(genre.id))
.map(genre => genre.name);
const matchedGenres = matchGenres(genres, genreList);
console.log(matchedGenres);
But! its not showing up in the component on render.
Its because your function doesn't return anything. You return inside filter and map and your function does not return anything. Also note that forEach always return undefined
You just need a minor change. Try this
let genres = ["1", "2", "3"];
let genreList = [{
id: "2",
name: "Two"
}, {
id: "32",
name: "Three"
}]
matchGenres = (genres, genreList) => {
return genreList.filter((list) => {
// findIndex return array index if found else return -1 if not found
return genres.findIndex(genere => genere === list.id) > -1;
}).map(list => list.name);
}
console.log(matchGenres(genres, genreList));
This is the solution that ended up working:
if (genreList.length !== 0) {
return genres.map(genre => genreList.find(list => list.id === genre)).map((newList) => newList.name) + ',';
}
For some reason the value of GenreList, which is an array, was showing up as empty for the first couple times the function is call. Thats another problem I'll have to look at but the if statement solves for it for the time being.

Categories