How to force ReactNative ListView to update a row when row-data change?

走远了吗. 提交于 2019-12-12 08:39:04

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!