How to get ReactNative ListView to update row when row data changes?

The ReactNative ListView is a little weird when it comes to updating data.

I have an array of objects (name: Item) that have 3 properties:

  • id (int)
  • name (string)
  • enabled (bool)

An array of items is displayed in the ListView. Each line shows the name of the element and a checkmark indicating the enabled state (red to true, gray to false). In my example, the simple string "true" / "false" is simplified.

Each row is embedded in TouchableOpacity using the onPress-EventHandler, which toggles the allowed state of the presented element.

Unfortunately, ListView will not update the row.

When debugging the rowHasChanged-Event, it turns out that the rowHasChanged-Event can never determine the changed state, since both rows already receive a new state after switching the enabled property without any update to the user interface.

Here is my code:

Class Element: 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} } 

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 behavior so that ListView is updated when row data changes?

+5
source share
2 answers

I think the problem is that the ListView data source is not updated when item updated. item passed to toggleItemEnabled refers to state.datasource , while you set the data source for props.items

Solution 1

Clone lines and set lines again

 toggleItemEnabled(item, sectionId, rowId) { newItems = this.props.items.slice(); newItems[rowId].enabled = !newItems[rowId].enabled; this.updateListView(newItems); } 

Decision 2

So I would prefer. I usually create a separate component for Row , so you should reorganize 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 the rendering method call this Row component

 renderItem(item) { return( <Row data={item} /> ); } 
+6
source

Maybe something is immutable, 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 "cut" is not needed

0
source

Source: https://habr.com/ru/post/1261997/


All Articles