My code works, but I have a best practice question: I have an array of objects in the state, and a user interaction will change a value of one object at a time. As far as I know, I'm not supposed to change the state directly, i should always use setState
instead. If I want to avoid that with any price, I will deep clone the array by iteration, and change the clone. Then set the state to the clone. In my opinion avoiding to change the state that I will change later anyway is just decreasing my performance.
Detailed version:
this.state.data is an array of objects. It represents a list of topics in a forum, and a Favorite button will toggle, calling clickCollect()
.
Since I have an array in the state, when I change the is_collected property of one item, I need to create a copy of the array to work with, and after changing to the new value, I can set it to the state.
var data = this.state.data.slice(0); data[index].is_collected = !data[index].is_collected; this.setState({data: data});
var data = this.state.data
: This would copy the pointer to the array and push(), shift(), etc would alter the state directly. Both data
and this.state.data
will be affected.
var data = this.state.data.slice(0)
: This makes a shallow clone, push and shift doesn't change the state but in my clone I still have pointers to the elements of the state's array. So if I change data[0].is_collected
, this.state.data[0].is_collected
gets changed as well. This happens before I call setState()
.
Normally I should do:
var data = []; for (var i in this.state.data) { data.push(this.state.data[i]); }
Then I change the value at index, setting it to true when it's false or false when it's true:
data[index].is_collected = !data[index].is_collected;
And change state:
this.setState({data: data});
Consider my array is relatively big or enormously big, I guess this iteration will reduce the performance of my APP. I would pay that cost if I knew that it is the right way for any reason. However, in this function (clickCollect
) I always set the new value to the state, I'm not waiting for a false API response that would say to stop making the change. In all cases, the new value will get into the state. Practically I call setState
only for the UI to render again. So the questions are:
for var i in ...
).slice(0)
) if my array contains objects? The changes are being made on the objects inside of the array, so the shallow clone still changes my state, just like a copy (data = this.state.data
) would do. My code is simplified and API calls are cut out for simplicity.
This is a beginner's question, so a totally different approach is also welcome. Or links to other Q & A.
import React from 'react'; var ForumList = React.createClass({ render: function() { return <div className="section-inner"> {this.state.data.map(this.eachBox)} </div> }, eachBox: function(box, i) { return <div key={i} className="box-door"> <div className={"favorite " + (box.is_collected ? "on" : "off")} onTouchStart={this.clickCollect.bind(null, i)}> {box.id} </div> </div> }, getInitialState: function() { return {data: [ { id: 47, is_collected: false }, { id: 23, is_collected: false }, { id: 5, is_collected: true } ]}; }, clickCollect: function(index) { var data = this.state.data.slice(0); data[index].is_collected = !data[index].is_collected; this.setState({data: data}); } }); module.exports = ForumList;
Personally I don't always follow the rule, if you really understand what you are trying to do then I don't think it's a problem.
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
In this case, mutating state and calling the setState
again like this is fine
this.state.data[index].is_collected = !this.state.data[index].is_collected;
this.setState({data: this.state.data});
The reason you should avoid mutating your state is that if you have a reference to this.state.data
, and calling setState
multiple times, you may lose your data:
const myData = this.state.data
myData[0] = 'foo'
this.setState({ data: myData })
// do something...
// ...
const someNewData = someFunc()
this.setState({ data: someNewData })
myData[1] = 'bar' // myData is still referencing to the old state
this.setState({ data: myData }) // you lose everything of `someNewData`
If you really concerned about this, just go for immutable.js