React Native - How to prepend AND append data in ListView without full re-render
Asked Answered
C

1

6

I've been working really hard to solve this problem for a long time now.

I've looked at StackOverflow AND other issues for a solution but I couldn't find a clear one.

I know that I can prepend WITH full re-render, but I don't know how I can prepend WITHOUT full re-render.

I think I can achieve this with non index key when I upload data into ListView, but I don't know exactly how or where to assign non index key. (non-index meaning the keys ListView uses to compare old rows and new rows DO NOT depend on just the index of the array of datasource)

So these are my questions.

  1. How do I assign non-index key?

  2. If I DO succeed in assigning non-index keys, then would I be able to make BOTH prependable AND appendable ListView that DOES NOT re-render all rows in both cases?

  3. If not, then how are you solving this problem?? Because this seems like a very common issue and I am very surprised that ListView does not have this functionality yet.

*Also, I've looked at PrependableListView on GitHub, but it seems like an exact copy of ListViewDataSource.. Am I missing something? If there is something that I missed in PrependableListView, then can somebody please point out where?

To help understand my problem, below is the test code I'm using.

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  ListView,
  RefreshControl,
  View
} from 'react-native';

export default class Test1 extends Component {
  constructor(props){
    super(props);
    const arr = [];
    this.state = {
          dataSource: new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}),
          refreshing: false,
        };
  }

  componentWillMount(){
    this.arr = [];
    for(let i = 0; i < 16; i++){
        this.arr.push({keyy: i, data: "row " + i});
    }

    this.setState({
        dataSource: this.state.dataSource.cloneWithRows(this.arr),
    });
  }

  _onRefresh() {
    this.setState({refreshing: true});
    let i = this.arr.length;
    let arr1 = [];
    arr1.unshift({keyy: i, data: "row " + i});
    for(var index = 0; index < this.arr.length; index++){
        arr1.push(this.arr[index]);
    }
//    console.log(this.arr);
//    console.log("arr1  :  " + arr1.length);
    this.setState({
       dataSource: this.state.dataSource.cloneWithRows(arr1),
       refreshing: false,
    });
  }

  _renderRow(rowData: string, sectionID: number, rowID: number){
//    console.log(rowData.data + "   :   " + sectionID + "   :   " + rowID);
    return(
        <Text style={{fontSize: 50}}>{rowData.data}</Text>
    );
  }

  _onEndReached(){
    let i = this.arr.length;
    let arr1 = [];
    for(var index = 0; index < this.arr.length; index++){
        arr1.push(this.arr[index]);
    }
    arr1.push({keyy: i, data: "row " + i});

    this.setState({
        dataSource: this.state.dataSource.cloneWithRows(arr1),
    });
  }

  render() {
    return (
      <ListView
            dataSource={this.state.dataSource}
            renderRow={this._renderRow.bind(this)}
            refreshControl={
              <RefreshControl
                refreshing={this.state.refreshing}
                onRefresh={this._onRefresh.bind(this)}
              />
            }
            onEndReachedThreshold={0}
            onEndReached={this._onEndReached.bind(this)}
      />
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('Test1', () => Test1);

As you can see in the code, I'm 'unshifting' a row in _onRefresh function, and 'pushing' a row in _onEndReached function. Mind you that this WILL work, but with a full re-render on _onRefresh. What I need is ONLY re-rendering the row I'm unshifting or pushing.

Also, this is what I think how this works.

When I compare two dataBlobs as below.. old : [0,1,2,3,4,5] new : [6,0,1,2,3,4,5]

default checker in ListView.DataSource will assign keys (or RowIDs) as the indices of dataBlob array by default. (newsource.rowIdentities.push(Object.keys(dataBlob[sectionID])) Which means in the case shown above, new dataBlob will have [0,1,2,3,4,5,6] as keys, then compare newDataBlob[sectionID][0] with oldDataBlob[sectionID][0] --> 6 != 0 --> re-render newDataBlob[sectionID][1] with oldDataBlob[sectionID][1] --> 0 != 1 --> re-render newDataBlob[sectionID][2] with oldDataBlob[sectionID][2] --> 1 != 2 --> re-render newDataBlob[sectionID][3] with oldDataBlob[sectionID][3] --> 2 != 3 --> re-render newDataBlob[sectionID][4] with oldDataBlob[sectionID][4] --> 3 != 4 --> re-render newDataBlob[sectionID][5] with oldDataBlob[sectionID][5] --> 4 != 5 --> re-render newDataBlob[sectionID][6] with oldDataBlob[sectionID][6] --> oldDataBlob[sectionID][6] undefined --> re-render

Candescent answered 3/11, 2016 at 8:58 Comment(0)
O
0

Try use FlatList instead of deprecated ListView. Possible that it won't have such problem...

Orris answered 21/2, 2018 at 9:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.