React Native ListView - rowHasChanged not working

I am trying to implement an infinite scroll in React Native. The following 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 add (or add) new data, the rowHasChanged DataSource method rowHasChanged not work. It simply re-displays each row, even if nothing has changed (except for new data). Any idea why the method is getting around?

+7
react-native
source share
4 answers

Edit: Pass the setState function to avoid race conditions

I just figured it out. If you have the same problem, check the point at which you changed your state with the new dataSource . My was like this:

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

Instead, you should always use dataSource from the previous state, for example:

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

Hooray!

+12
source share

it worked for me, hope it helps. I created a new data source and assigned it updated data when the state changed as follows: `

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

Now new data is assigned and the view is displayed correctly. Note that the record array, which is now assigned, contains updated data. Still interesting though, if this is the best way to do it, but it works!

+3
source share

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

However, when I set State this way, rowHasChanged receives a call for all rows, however rowHasChanged always returns false and no rows are displayed. ?? 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 calling rowHasChanged . What is right?

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

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

+1
source share

for those who still have problems with rowHasChanged but still returns false , the following snippets may help

The data source is initialized as usual:

 let ds = new ListView.DataSource ({ rowHasChanged: (a, b) => { const changed = (a !== b) return changed } }) this.data = [] this.state = { listDataSource: ds.cloneWithRows(this.data) } 

here is the function that will update the line

 updateRow = (row, rowId, sectionId) => { // make a shallow clone from the stored data array let blob = this.data.concat() // modify the row, since we are using the triple equal operator, we need to make sure we are giving it a new object (new ref) blob[rowId] = Object.assign({}, blob[rowId], {label: blob[rowId].label + '..cape..deh'}) // tell react to update the source this.setState({ listDataSource: this.state.listDataSource.cloneWithRows(blob) }, () => { // we need to update our data storage here! after the state is changed this.data = blob }) } 
0
source share

All Articles