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:
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.
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}
}
'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?
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} />
);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With