Reputation: 43
I have a list (news), it comes from the server and each object has multiple fields that the user can edit. I'm saving the list in a state called originalData
, and I'm saving the first news item in a state called activeNews
:
componentDidMount(){
fetch('/api/published/news_published_english').then(res => res.json()).then(news =>{
if(news && news.rows[0]){
const new_news = [...news.rows];
this.setState({originalData:[...news.rows]});
this.setState({activeNews:new_news[0].doc.content});
}
});
}
When the user clicks on another news item, activeNews
will change to the selected index. handleChange
takes the index the of the field that the user want to change within the active news item and its value:
handleChange(index, value) {
const x = [...this.state.news];
x[index].value = value;
this.setState({ news: x });
console.log("original", this.state.originalData);
console.log("news", news);
}
Now the problem is why originalData
keeps updating with the values that the user changes? I want originalData
to not change but it keeps changing in the handleChange function!
This is the only place where I called setState
on originalData
but it keeps changing to the value that is getting passed to handleChange
.
this.setState({ originalData: [...news.rows] });
Upvotes: 1
Views: 1142
Reputation: 14023
In your code, x[index].value = value
is modifying the original array, which is why it is broken. You could write your change handler like this:
handleChange(index, value) {
this.setState(state => (
{ news: state.news.map((item, i) => i === index
? { ...item, value }
: item
}
));
}
Note that I am using a callback function in setState, this is recommended when your new state is dependend on previous state.
EDIT Actually, there is no need to map over the whole array, because you already know the index. Look at caesay's answer which is better performancewise:
handleChange(index, value) {
this.setState(state => {
const newsCopy = [ ...state.news ];
newsCopy [index] = { ...arr[index], value };
return { news: newsCopy };
});
}
Upvotes: 1
Reputation: 17233
You're mutating the original objects in your handleChange
function when you do this:
x[index].value = value;
To better show what's happening, I'm going to expand your code a bit
handleChange(index, value) {
const copiedArray = [...this.state.news];
const originalObject = copiedArray[index];
originalObject.value = value;
this.setState({ news: copiedArray });
}
As I've eluded to with the variable names here, even though you've created a new array - the object at this.state.news[index]
and copiedArray[index]
are the same object - so changing .value
will change in both places. In order to solve this, you need to do a deep clone. You can do this by finding a library (like lodash) which can do this, or you can take a quicker solution and change your code as follows:
handleChange(index, value) {
const copiedArray = [...this.state.news];
const originalObject = copiedArray[index];
const copiedObject = Object.assign({}, originalObject);
copiedObject.value = value;
copiedArray[index] = copiedObject;
this.setState({ news: copiedArray });
}
Or a condensed version of the same thing:
handleChange(index, value) {
const arr = [...this.state.news];
arr[index] = { ...arr[index], value };
this.setState({ news: arr });
}
Upvotes: 3
Reputation: 4318
This is a common mistake in react apps, you are creating a copy of the array this.state.news
which is an array of objects.
When you do x[index].value = value
you are actually modifying the original object that is still being referenced in this.state.news
.
This --> const x = [...this.state.news];
makes a copy of the array but not the objects, they are still referencing the original objects.
What you need to be doing is something like this:
const news = this.state.news.map((x, i) => {
if (i === index) {
return {...x, value};
} else return x
})
this.setState({ news })
Upvotes: 2