11

Is it possible to force a ListView to re-render, even if the data in the dataSource has not changed? I have a ListView within a tab bar in my app and I want it to redraw every time that tab is selected, regardless of if the data is the same or has changed.

this.state = {
  data: props.data,
  dataSource: new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})
}

componentWillMount() {
  this.setState({
    dataSource: this.state.dataSource.cloneWithRows(nextProps.data)
  })
}

render() {
  <ListView
   dataSource={this.state.data}
   renderRow={this._renderRow}
  />
}

I tried playing with the rowHasChanged arguments but that did not help. Any help would be much appreciated

1
  • Maybe add on an onClick function to the tab and when that function is called make it call the render function? Commented Jun 23, 2016 at 19:58

5 Answers 5

35

So your use-case, as I understand it, is to re-render all rows of ListView because of value changes. I don't know what value is, up to you to determine it, but I will use value to explain my answer:

There are a few ways to solve this. each have pros and cons:

  • easy way: just <ListView key={value} ... /> !! It tells React that ListView needs to get re-render when value changes (it will be unmounted, remounted).
  • "hacky way". replace with a new DataSource. this.setState({ dataSource: new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }).cloneWithData(this.props.data) });
  • the (imo) best practice way: in the data, incorporate value and implements rowHasChanged properly. For instance: (r1, r2) => r1.value !== r2.value && r1.data !== r2.data (<- this is JUST a possible way to represent the data, you have to chose your format, I just assumed you did clone dataSource with nextProps.data.map(data => ({ data, value })).

to me, the third and last solution is the best because:

  • it scales nicely to more condition of re-rendering (other values)
  • it can happen that only part of your rows actually should get re-rendered, and, in that case, you shouldn't re-render all rows for better performance.

here is an other answer I gave on that subject:

What are the exact inputs to rowHasChanged in ListView.DataSource

Sign up to request clarification or add additional context in comments.

6 Comments

Thank you for your answer, my mind is blown. I had no idea r1 and r2 meant old and new row. Makes so much more sense now. Wish they could have referenced that better!
There are some cases where the "hacky way" seems to be the only way to do what you need. For instance, I'm working on a case where a model in the list of models displayed by the ListView is modified somewhere else in the app. In order to show this change, we have to modify the entire DataSource, because the ListView will just do nothing if you don't. It's a developer opinion from React that an entire list should be considered as immutable as the objects inside that list.
@mienaikoe implementing rowHasChanged is actually the opportunity to choose if you consider your list object immutable or not. It's really up to you to implement it and only the default suggested r1!==r2 is a "developer opinion from React" in favor of immutability objects but the ListView itself is not that opinion (instead of the experimental WindowedListView for instance). rowHasChanged gives you control of the equality function and immutability or not of your data. BTW this whole thing is a performance tradeoff and maybe we'll find something better in the future.
like @mienaikoe says the hacky way helps at time. and also to note the answer has cloneWithData which didn't work for me so had to change to cloneWithRow thanks @gre
I always thought r1 and r2 were different rows akin to sort((a, b) => .... Now that I realize it's prevRow vs. nextRow, the rowHasChanged function finally makes sense to me!
|
2

Use an Immutability Approach

The best way to "update" this would be to clone the data item in question rather than doing a flag hack as the r1 !== r2 is already hinting at. Instead of updating an individual item, change the "pointer" itself by cloning.

For this particular issue update the item in the array like so.

arr[i] = { ...arr[i], someKey: newValue }

This will replace the "someKey" property with "newValue" and the current row will have a new pointer.

Here is a good resource to learn about immutability in modern JavaScript that react native uses: https://wecodetheweb.com/2016/02/12/immutable-javascript-using-es6-and-beyond/

Comments

0

You can call this.forceUpdate() to force a re-render.

You can do this in a simple onClick handler that you attach to the tab.

More info here, on the official docs

However, if there are other rules dictating if there should be an update or not, you can try replacing the dataSource altogether with a new one, making it have a new reference. That will also make it update.

Something along the lines of

this.setState({ data : newDataSource… });

Comments

0

This is probably a bit overkill but I'm just replacing the datasource on componentDidUpdate (maybe this is bad for performance? I dunno haha)

  componentWillMount() {
   this.ds = new ListView.DataSource({
     rowHasChanged: (r1, r2) => r1 !== r2,
    });
   this.dataSource = this.ds.cloneWithRows(this.props.shiftsList);
  }

componentDidUpdate() {
  this.ds = new ListView.DataSource({
   rowHasChanged: (r1, r2) => r1 !== r2,
  });
 this.dataSource = this.ds.cloneWithRows(this.props.shiftsList);
}

Comments

0

I know I'm a bit late but I'm sure that this is still relevant. There's an even easier approach to this... The first example provided by Jason is by all means a good solution but I'm thinking of those whom easily gets confused. If so, use this.

// On component initalization
componentWillMount() {
    this.UpdateData();
}

// When the component updates
componentDidUpdate() {
    if (this.state.data != this.props.data)
        this.UpdateData();
}

// Function to update the ListView component data
UpdateData() {
    var source = new ListView.DataSource({
        rowHasChanged: (r1, r2) => r1 !== r2
    });

    this.setState({
        data: this.props.data,
        dataSource: source.cloneWithRows(this.props.data)
    });
}

I also recommend to implement Redux in your application. That way you could store the new, updated array value in a reducer and then store it locally in a state and later use an if statement in the componentDidUpdate function to determine whether the data is updated or not.

The React debugger logs no performance based complaints or warnings whatsoever...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.