Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Native ListView - rowHasChanged doesn't fire

Tags:

react-native

I am trying to implement an infinite scroll in React Native. Below is the source of the component:

var React = require('react-native');
var server = require('../server');
var Post = require('./Post');
var SwipeRefreshLayoutAndroid = require('./SwipeRefreshLayout');
var backEvent = null;
var lastPostId = "";
var isLoadingMore = false;
var isLoadingTop = false;
var onEndReachedActive = false;

var {
    StyleSheet,
    ListView,
    View,
    Text,
    Image,
    ProgressBarAndroid,
    BackAndroid
} = React;

class Stream extends React.Component {

constructor(props) {
    super(props);
    this.ds = new ListView.DataSource({
        rowHasChanged: (row1, row2) => {
            console.log("rowHasChenged FIRED!!");
            return false;
        }
    });

    this.state = {
        dataSource: this.ds.cloneWithRows(['loader']),
        hasStream: false,
        posts: []
    };

}

componentDidMount() {

    BackAndroid.addEventListener('hardwareBackPress', () => {
        this.props.navigator.jumpBack();
        return true;
    }.bind(this));

    server.getStream('', '', 15).then((res) => {

        lastPostId = res[res.length-1].m._id;

        this.setState({
            posts: res,
            hasStream: true,
            dataSource: this.ds.cloneWithRows(res)
        }, () => onEndReachedActive = true);
    })
}

onRefresh() {
    var posts = this.state.posts;
    var firstPost = posts[0].m._id;

    console.log(this.state.dataSource._rowHasChanged);

    isLoadingTop = true;

    server.getStream('', firstPost, 4000)
        .then(res => {
            console.log(posts.length);
             posts = res.concat(posts);
             console.log(posts.length);
             this.setState({
                dataSource: this.ds.cloneWithRows(posts),
                posts
             }, () => {
                this.swipeRefreshLayout && this.swipeRefreshLayout.finishRefresh();
                isLoadingTop = false;
             });
        }).catch((err) => {
            isLoadingTop = false;
        })

}

onEndReached(event) {

    if(!onEndReachedActive) return;

    if(this.state.loadingMore || this.state.isLoadingTop)return;
    isLoadingMore = true;
    var posts = this.state.posts;
    server.getStream(posts[posts.length-1].m._id, '', 15)
        .then(res => {
            console.log('received posts');
            posts = posts.concat(res);
            lastPostId = posts[posts.length-1].m._id;
            this.setState({
                dataSource: this.ds.cloneWithRows(posts),
                posts
            }, ()=>isLoadingMore = false);
        })
}

renderHeader() {
    return (
        <View style={styles.header}>
            <Text style={styles.headerText}>Header</Text>
        </View> 
    )
}

renderRow(post) {

    if(post === 'loader') {
        return (
            <ProgressBarAndroid 
                styleAttr="Large" 
                style={styles.spinnerBottom}/>
        )
    }

    let hasLoader = post.m._id === lastPostId;

    let loader = hasLoader ? 
        <ProgressBarAndroid 
            styleAttr="Large" 
            style={styles.spinnerBottom}/> : null;

    return (
        <View>
            <Post 
                post={post}/>
            {loader}
        </View> 
    )
}

render() {


    return (
                <ListView
                    style={styles.mainContainer}
                    dataSource={this.state.dataSource}
                    renderRow={this.renderRow.bind(this)}
                    onEndReached={this.onEndReached.bind(this)}
                    onEndReachedThreshold={1}
                    pageSize={15} />
    );
}
}

The problem is that whenever I append (or prepend) new data, the rowHasChanged method of the DataSource doesn't fire. It just re-renders every row, even tho nothing has changed (except the new data). Any idea why the method is bypassed?

like image 962
Rado Milushev Avatar asked Nov 23 '15 11:11

Rado Milushev


3 Answers

Edit: Pass a function to setState to avoid race conditions

I just figured it out. If you are having the same issue, check the point at which you change your state with the new dataSource. Mine was like this:

this.setState({
    dataSource: this.ds.cloneWithRows(posts)
});

Instead you should always use the dataSource from the previous state, like this:

this.setState(state => ({
    dataSource: state.dataSource.cloneWithRows(posts)
}))

Cheers!

like image 104
Rado Milushev Avatar answered Jan 04 '23 21:01

Rado Milushev


this worked for me, hope this helps. I created a new dataSource and assigned the updated data to it on state change as follows:`

var dataSource = new ListView.DataSource(
  {rowHasChanged: (r1, r2) => ( r1 !== r2)});

  this.setState({ dataSource : dataSource.cloneWithRows(posts) });

Now, the new data is assigned and the view is rendered correctly. Note that posts array that is assigned now holds the updated data. Still wondering though if it's the best way to do it but it works!

like image 35
Asim Avatar answered Jan 04 '23 21:01

Asim


I agree it seems to make sense that you should always use the dataSource from the previous state.

Yet when I setState this way, rowHasChanged gets called for all rows, however, rowHasChanged always returns false and no rows are rendered??? Why?

  // This is callback handler that the ListView DetailView will 
  // call when a ListView item is edited
  onChange(waypoint: Object){
    console.log('Callback: rowNumber= ', waypoint.rowNumber);
    console.log('        length(m)= ', waypoint.distance.meters);

    var itemListChanged = this.state.itemList;
    itemListChanged[waypoint.rowNumber-1] = waypoint;
    this.setState({
            dataSource: this.state.dataSource.cloneWithRows(itemListChanged),
    });
  },

If I setState this way, renderRow is called for all rows unconditionally without rowHasChanged ever being called. Which is correct?

this.setState({
  dataSource: ds.cloneWithRows(itemListChanged),
});

ListView, datasource, and react-native are a hard learning curve coming from C#/C/C++.

like image 37
Ed of the Mountain Avatar answered Jan 04 '23 19:01

Ed of the Mountain