问题
ReactNative's ListView is little bit odd when it comes to data updates.
I have an array of objects (name: Item), that have 3 properties:
- id (int)
- name (string)
- enabled (bool)
The array of items is displayed in a ListView. Each row shows the name of the item and a checkmark representing the enabled-state (red on true, gray on false). In my example it is simplified to a simple string "true"/"false".
Each row is embedded in a TouchableOpacity with a onPress-EventHandler, that toggles the enabled-state of the represented item.
Unfortunately, the ListView won't update the row.
While debugging the rowHasChanged-Event, it turns out that the rowHasChanged-Event could never determine the changed state because both rows already get the new state after toggling the enabled-property without any update to the UI.
Here is my Code:
The Item-Class: Item.js
export default class Item {
constructor(id, name, enabled) {
this._id = id;
this._name = name;
this._enabled = enabled;
}
get id() {return this._id}
get name() {return this._name}
set name(value) {this._name = value}
get enabled() {return this._enabled}
set enabled(value) {this._enabled = value}
}
The ListView:
'use strict';
import React, {PropTypes, Component} from 'react';
import {View,TouchableOpacity, TouchableHighlight,
Text, ListView, StyleSheet} from 'react-native';
import Item from './Item';
class ItemListView extends Component {
constructor(props) {
super(props);
this.updateListView = this.updateListView.bind(this);
this.toggleItemEnabled = this.toggleItemEnabled.bind(this);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 != row2
}),
};
}
componentDidMount() {
this.updateListView(this.props.items)
}
componentWillReceiveProps(nextProps) {
this.updateListView(nextProps.items)
}
updateListView(items) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(
items.slice() // copy items to a new array
),
});
}
toggleItemEnabled(item) {
item.enabled = !item.enabled;
this.updateListView(this.props.items);
}
renderItem(item) {
console.log('Render', item.enabled, item.name)
return (
<TouchableOpacity style={{flex:1}} onPress={ () => this.toggleItemEnabled(item) }>
<View style={s.row}>
<Text>{item.name + ' ' + item.enabled}</Text>
</View>
</TouchableOpacity>
)
}
render() {
return (
<View style={s.container}>
<ListView
style={s.listView}
dataSource={this.state.dataSource}
renderRow={(item) => this.renderItem(item)}
/>
</View>
)
}
}
ItemListView.propTypes = {
items: PropTypes.array
};
ItemListView.defaultProps = {
items: [],
};
export default ItemListView;
const s = StyleSheet.create({
container: {
},
row: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 12,
paddingBottom: 12,
paddingHorizontal: 30,
borderBottomWidth: 1,
borderBottomColor: 'gray',
},
innerContainer: {
alignItems: 'center',
},
});
How to fix this behaviour, that the ListView gets updated when row-data changes?
回答1:
I think the problem is the ListView
data source is not getting updated when you update item
. The item
passed to toggleItemEnabled
belongs to state.datasource
whereas you are setting the datasource for props.items
Solution 1
Clone the rows and set the rows again
toggleItemEnabled(item, sectionId, rowId) {
newItems = this.props.items.slice();
newItems[rowId].enabled = !newItems[rowId].enabled;
this.updateListView(newItems);
}
Solution 2
This is the way I would prefer. I generally create a separate component for Row
So you should refactor as follows
Create a new Row.js
constructor(props) {
super(props);
this.state = {
enabled: false;
};
}
toggleItemEnabled() {
this.setState({enabled: !this.state.enabled});
}
render() {
item = this.props.data;
return (
<TouchableOpacity style={{flex:1}} onPress={ () => this.toggleItemEnabled() }>
<View style={s.row}>
<Text>{item.name + ' ' + this.state.enabled}</Text>
</View>
</TouchableOpacity>
)
}
Then in your main component in render row method call this Row component
renderItem(item) {
return(
<Row data={item} />
);
}
回答2:
May be something with immutability, try changing the value this way
toggleItemEnabled(item) {
this.updateListView(this.props.items.map(
(i) =>
return (i.id() == item.id()) ? new Item(i.id(), i.name(), !i.enabled()) : i
)
);
}
with this 'slice' would not be necessary
来源:https://stackoverflow.com/questions/41369148/how-to-force-reactnative-listview-to-update-a-row-when-row-data-change