Update array in array of object using previous state in react - javascript

I have the following state in my app:
const [education, setEducation] = useState([
{
institution: "University",
area: "President",
studyType: "Bachelor",
startDate: "2013-01-01",
endDate: "2014-01-01",
gpa: "4.0",
courses: ["DB1101 - Basic SQL"],
},
]);
And the following method to update the state:
const onCoursesChange = (newValue, index) => {
const newValuesArr = newValue ? newValue.map(item => item.value) : [];
setEducation((prev)=>(
prev[index].courses=newValuesArr
));
//setEducation(education[index].courses = newValuesArr);
console.log(education[index].courses)
};
With the above code I get the error Cannot create property 'courses' on string 'asdfa' when the array newValuesArr has more than one element.
How do I update the state ??

The problem is here:
setEducation((prev)=>(
prev[index].courses=newValuesArr
));
prev[index].courses=newValuesArr is an assignment that simply returns the value being assigned, here newValuesArr. This is an array of strings, so its elements do not have the properties you expect.
Your state-updating function (the one passed to setEducation) instead needs a return the whole of prev, with the update you desire. And this should be done immutably - that is, without updating prev. Luckily, ES6 offers nice tools for doing this. Here is how you can do it in this case:
setEducation((prev)=>(
prev.map((item, oldIndex) => oldIndex === index ? { ...item, courses: newValuesarr } : item )
));
That is, it maps through the array prev, and leaves all unchanged apart from the one with the matching index, and in that one updates the courses property to the desired array.

Related

Updating Boolean Value in React State Array

Goal :
Within a React App, understand how to correctly update a boolean value that is stored within an array in state.
Question :
I am not sure if my code is correct in order to avoid asynchronous errors (whether or not it works in this case I am not interested in as my goal is to understand how to avoid those errors in all cases). Is there some syntax that is missing in order to correctly create a new state?
Context :
Creating a todo list within React.
My state consists of an array labeled itemsArr with each array element being an object
The objects initialize with the following format :
{ complete : false, item : "user entered string", id : uuid(), }
Upon clicking a line item, I am calling a function handleComplete in order to strikethrough the text of that line by toggling complete : false/true
The function is updating state via this :
handleComplete(id){
this.setState(state =>
state.itemsArr.map(obj => obj.id === id ? obj.complete = !obj.complete : '')
)
}
Additional Details :
One of my attempts (does not work) :
handleComplete(id){
const newItemsArr = this.state.itemsArr.map(obj =>
obj.id === id ? obj.complete = !obj.complete : obj);
this.setState({ itemsArr : newItemsArr })
}
In both snippets you haven't correctly returned a new object from the .map callback:
handleComplete(id){
const newItemsArr = this.state.itemsArr.map(obj =>
obj.id === id ? { id: obj.id, complete: !obj.complete } : obj);
this.setState({ itemsArr : newItemsArr });
}
Your function, as mentioned above, returns and updates wrong data, by adding new elements in your state instead of updating your current array.
Since array.map() returns an array, you can assign it to the state array, itemsArr.
You should also replace the change condition by updating the element's value first, and then returning it, if its id matches,or else, simply leave it as is.
handleComplete=(id)=>{
this.setState(state =>{
itemsArr:state.itemsArr.map(obj => obj.id === id
? (obj.complete = !obj.complete,obj) //update element's complete status and then return it
: obj) //return element as is
},()=>console.log(this.state.itemsArr)) //check new state
}
live example using your data : https://codepen.io/Kalovelo/pen/KKwyMGe
Hope it helps!
state = {
itemsArr: [
{
complete: false,
item: 'iemName',
id: 1
},
{
complete: false,
item: 'iemName',
id: 2
}
]
}
handleComplete = (id) => {
let { itemsArr } = { ...this.state };
itemIndex = itemsArr.findIndex(item => id === item.id);
itemsArr[itemIndex]['complete'] = !itemsArr[itemIndex]['complete'];
this.setState{itemsArr}
}

How do I update the value of an object property inside of an array in React state

I cannot seem to find an answer on here that is relevant to this scenario.
I have my state in my React component:
this.state = {
clubs: [
{
teamId: null,
teamName: null,
teamCrest: null,
gamesPlayed: []
}
]
}
I receive some data through API request and I update only some of the state, like this:
this.setState((currentState) => {
return {
clubs: currentState.clubs.concat([{
teamId: team.id,
teamName: team.shortName,
teamCrest: team.crestUrl
}]),
}
});
Later on I want to modify the state value of one of the properties values - the gamesPlayed value.
How do I go about doing this?
If I apply the same method as above it just adds extra objects in to the array, I can't seem to target that specific objects property.
I am aiming to maintain the objects in the clubs array, but modify the gamesPlayed property.
Essentially I want to do something like:
clubs: currentState.clubs[ index ].gamesPlayed = 'something';
But this doesn't work and I am not sure why.
Cus you are using concat() function which add new item in array.
You can use findIndex to find the index in the array of the objects and replace it as required:
Solution:
this.setState((currentState) => {
var foundIndex = currentState.clubs.findIndex(x => x.id == team.id);
currentState.clubs[foundIndex] = team;
return clubs: currentState.clubs
});
I would change how your state is structured. As teamId is unique in the array, I would change it to an object.
clubs = {
teamId: {
teamName,
teamCrest,
gamesPlayed
}
}
You can then update your state like this:
addClub(team) {
this.setState(prevState => ({
clubs: {
[team.id]: {
teamName: team.shortName,
teamCrest: teamCrestUrl
},
...prevState.clubs
}
}));
}
updateClub(teamId, gamesPlayed) {
this.setState(prevState => ({
clubs: {
[teamId]: {
...prevState.clubs[teamId],
gamesPlayed: gamesPlayed
},
...prevState.clubs
}
}));
}
This avoids having to find through the array for the team. You can just select it from the object.
You can convert it back into an array as needed, like this:
Object.keys(clubs).map(key => ({
teamId: key,
...teams[key]
}))
The way I approach this is JSON.parse && JSON.stringify to make a deep copy of the part of state I want to change, make the changes with that copy and update the state from there.
The only drawback from using JSON is that you do not copy functions and references, keep that in mind.
For your example, to modify the gamesPlayed property:
let newClubs = JSON.parse(JSON.stringify(this.state.clubs))
newClubs.find(x => x.id === team.id).gamesPlayed.concat([gamesPlayedData])
this.setState({clubs: newClubs})
I am assuming you want to append new gamesPlayedData each time from your API where you are given a team.id along with that data.

How to update certain object in state array based on received key from DOM

I am confused about executing spread operator and using it to update state array like that
todos: [
{
id: "1",
description: "Run",
completed: "true"
},
{
id: "2",
description: "Pick John",
completed: "false"
}]
I have objects inside my array, the examples provided after searching are using spread operator to update arrays with single object, how can I update that object with "id" that equals "key" only. My wrong function is
markTaskCompleted(e) {
var key = e._targetInst.key;
this.setState(
{
todoList: // state.todoList is todos variable
[...this.state.todoList, this.state.todoList.map(task => {
if (task.id == key) task.completed = "true";
}) ]
},
this.getTodos
);
}
The result of this array is the same array of todos (spread operator) with array of undefined items.
I have been googling for some time but couldn't really get it.
Instead of destructuring the array and using map, I typically update a single item's value with a single map that replaces the item I am updating and returns the existing value for all other items. Something like this:
this.setState((prevState) => {
return {
todoList: prevState.todoList.map((task) => {
if (task.id === key) {
return { ...task, completed: true };
} else {
return task;
}
}),
};
});
Also, notice that this example passes a function to this.setState rather than an object. If you are updating the state based on the previous state (in this example using todoList from the previous state) you should use the function method. setState is asynchronous and you could get unexpected results from using this.state to compute the new state.

How to delete object from array using object property - React

I have a todo list that holds a delete button in a grandchild, that when clicked fires an event in the parent - I am wanting this event to delete the array entry corresponding to the grandchild clicked.
Parent (contains the array and my attempt at the function)
const tasks = [
{ name: 'task1', isComplete: false },
{ name: 'task2', isComplete: true },
{ name: 'task3', isComplete: false },
]
// taskToDelete is the name of the task - doesn't contain an object
deleteTask(taskToDelete) {
this.state.tasks.remove(task => task.name === taskToDelete);
this.setState({ tasks: this.state.tasks });
}
Any help would be appreciated
Two issues there:
You're seeming to try to direct modify this.state.tasks. It's important not to do that, never directly modify this.state or any object on it. See "Do Not Modify State Directly" in the React documentation for state.
You're passing an object to setState that is derived from the current state. It's important never to do that, too. :-) Instead, pass setState a function and use the state object it passes you when calling that function. From "State Updates May Be Asynchronous" in the documentation:
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state... [Instead]...use a second form of setState() that accepts a function rather than an object.
(my emphasis)
I figure your remove on an array was intended to be hypothetical, but for the avoidance of doubt, arrays don't have a remove method. In this case, the best thing to do, since we need a new array, is to use filter to remove all entries that shouldn't still be there.
So:
deleteTask(taskToDelete) {
this.setState(prevState => {
const tasks = prevState.tasks.filter(task => task.name !== taskToDelete);
return { tasks };
});
}
You could simply filter the array :
this.setState(prevState => ({
tasks: prevState.tasks.filter(task => task.name !== 'taskToDelete')
}));
Also when updating based on this.state, its better to use the function form because setState is async.
You can use filter to remove one object from an array following the immutable pattern (filter will create a new array) :
deleteTask(taskToDelete) {
const newTaskArray = this.state.tasks.filter(task => task.name !== taskToDelete);
this.setState({ tasks: newTaskArray });
}
Edit : codepend of the solution : https://codepen.io/Dyo/pen/ZvPoYP
You can implement deleteTask method as below:
deleteTask(taskToDelete) {
this.setState((prevState, props) => {
const tasks = [...prevState.tasks];
const indexOfTaskToDelete = tasks.findIndex(
task => task.name === taskToDelete
);
tasks.splice(indexOfTaskToDelete, 1);
return { tasks };
});
}
A. Find the index of taskToDelete.
B. Then use splice method to delete the item from the collection
C. Then call setState to update the state with tasks.
You can use higher order function Array#filter to delete the task.
let updatedTasks = this.state.tasks.filter(task => task.name !== taskToDelete);
this.setState({ tasks: updatedTasks });
I have followed below steps to delete a particular selected Object from the state array:
Here I am using a list of checkBoxes, when I am selecting a checkBox it will add it in the state array and when it gets de-selected then it will get deleted from the array.
if (checked) {
var tempObject = { checkboxValue: data, label: title }
this.state.checkBoxState.push(resTemp);
} else {
var element = data; //data is coming from different method.
for (let index = 0; index < this.state.checkBoxState.length; index++) {
if (element === this.state.checkBoxState[index].checkboxValue) {
this.state.checkBoxState.splice(index, 1);
}
}
}
I got stuck for this question and I am sharing my solution. Hope it will help you.

Filtering Arrays in a Reducer - Redux

Following a React tutorial, I see this code in a reducer to remove a message from an array using its ID:
Wouldn't this be better written as:
else if (action.type === 'DELETE_MESSAGE') {
return {
messages: [
...state.messages.filter(m => m.id === action.id)
],
};
};
I thought for a second that filter might modify state and return the same array but according to MDN it creates a new array.
Am I safe, and is my implementation correct?
Yes. It would actually be a very clean solution. The trick is that, in Array#filter, every element of an array is applied with a function that accepts more than one argument. Such a function, when returns a boolean value, is called predicate. In case of Array#filter (and in fact, some other methods of Array.prototype), the second argument is index of the element in source array.
So, given that you know the index, it's simply
(state, action) => ({
...state,
messages: state.messages.filter((item, index) => index !== action.index)
})
However, you don't know the index. Instead, you have a value of id property. In this case, you're right, you simply need to filter the source array against this id to only put elements that have value of id that is not equal to target id:
(state, action) => ({
...state,
messages: state.messages.filter(item => item.id !== action.id)
})
Take a note: no need to spread state.messages and put them back into a new array. Array#filter doesn't mutate the source array, which is nice.
So, it's !== instead of ===. You were close.

Categories