问题
I have an iOS app I am making with react-native. The Game class contains a ListView component. I set the state in the constructor and include a dataSource
. I have a hardcoded array of data for right now that I store in a different state property (this.state.ds
). Then in the componentDidMount
I use the cloneWithRows
method to clone my this.state.ds
as my dataSource for the view. That is pretty standard as far as ListViews go and is working well. Here is the code:
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/
'use strict';
var React = require("react-native");
var { StyleSheet, Text, View, ListView, TouchableHighlight } = React;
class Games extends React.Component {
constructor(props) {
super(props);
var ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 != r2
});
this.state = {
ds: [
{ AwayTeam: "TeamA", HomeTeam: "TeamB", Selection: "AwayTeam" },
{ AwayTeam: "TeamC", HomeTeam: "TeamD", Selection: "HomeTeam" }
],
dataSource: ds
};
}
componentDidMount() {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.state.ds)
});
}
pressRow(rowData) {
var newDs = [];
newDs = this.state.ds;
newDs[0].Selection = newDs[0] == "AwayTeam" ? "HomeTeam" : "AwayTeam";
this.setState({
dataSource: this.state.dataSource.cloneWithRows(newDs)
});
}
renderRow(rowData) {
return (
<TouchableHighlight
onPress={() => this.pressRow(rowData)}
underlayColor="#ddd"
>
<View style={styles.row}>
<Text style={{ fontSize: 18 }}>
{rowData.AwayTeam} @ {rowData.HomeTeam}{" "}
</Text>
<View style={{ flex: 1 }}>
<Text style={styles.selectionText}>
{rowData[rowData.Selection]}
</Text>
</View>
</View>
</TouchableHighlight>
);
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
/>
);
}
}
var styles = StyleSheet.create({
row: {
flex: 1,
flexDirection: "row",
padding: 18,
borderBottomWidth: 1,
borderColor: "#d7d7d7"
},
selectionText: {
fontSize: 15,
paddingTop: 3,
color: "#b5b5b5",
textAlign: "right"
}
});
module.exports = Games
The issue I am having comes in the pressRow
method. When the user presses the row, I would like the selection to change and for it to render the change on the device. Through some debugging, I have noticed that even though I am changing the Selection
property of the object in the newDs
array, the same property changes on the object in this.state.ds
and similarly changes the object in this.state.dataSource._dataBlob.s1
. Through further debugging, I have found that since those other arrays have changed, the ListView's DataSource object doesn't recognize the change because when I set the state and rowHasChanged
is called, the array it is cloning matches the array this.state.dataSource._dataBlob.s1
and so it doesn't look like a change and doesn't rerender.
Any ideas?
回答1:
Try this:
pressRow(rowData){
var newDs = [];
newDs = this.state.ds.slice();
newDs[0].Selection = newDs[0] == "AwayTeam" ? "HomeTeam" : "AwayTeam";
this.setState({
dataSource: this.state.dataSource.cloneWithRows(newDs)
})
}
This should make a copy of the array, which can then be modified independently of the original array in this.state.ds
.
回答2:
In case anyone comes across this issue like I did, here's the complete solution.
Basically, there seems to be a bug with the ListView component and you need to rebuild each item that changes in the datasource for the ListView to redraw it.
First, create the datasource and a copy of the array and save them to state. In my case, foods
is the array.
constructor(props){
super(props);
var ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
});
this.state = {
dataSource: ds.cloneWithRows(foods),
db: foods,
};
}
When you want to change something in the datasource, make a copy of the array you saved to the state, rebuild the item with the change and then save the new array with the change back to the state (both db and datasource).
onCollapse(rowID: number) {
console.log("rowID", rowID);
var newArray = this.state.db.slice();
newArray[rowID] = {
key: newArray[rowID].key,
details: newArray[rowID].details,
isCollapsed: newArray[rowID].isCollapsed == false ? true : false,
};
this.setState({
dataSource: this.state.dataSource.cloneWithRows(newArray),
db: newArray,
});
}
回答3:
react is smart enough to detect changes in dataSource and if the list should be re-rendered. If you want to update listView, create new objects instead of updating the properties of existing objects. The code would look something like this:
let newArray = this._rows.slice();
newArray[rowID] = {
...this._rows[rowID],
Selection: !this._rows[rowID].Selection,
};
this._rows = newArray;
let newDataSource = this.ds.cloneWithRows(newArray);
this.setState({
dataSource: newDataSource
});
You can read more about similar issue on Github
来源:https://stackoverflow.com/questions/31738671/react-native-updating-list-view-datasource