4

I have two issues when using a FlatList (that both work with a ListView).

I have a list that is displayed via a display property for each item and then checked in my FlatList like so:

<FlatList
  ref='flatList'
  data={this.state.dataSource}
  enableEmptySections
  renderItem={({item}) => item.display && <Text>{item.text}</Text>} />

When I change the display property, it doesn't display however. This worked fine in the ListView setup.

My second problem is that scrollToEnd is firing an undefined error on "this._listref" which is within the scrollToEnd function. I pass no parameters to it.

setTimeout(this.refs.flatList.scrollToEnd, 0)

My questions are, how do I update the data in my array for the list so it changes in the list, and the second question is how do I fix the scrollToEnd error?

Update 1: (data removed, not needed anymore)

Update 2: Problem ONE solved, the list needs "extraData" comparison to see if it updates. The fixed edition:

<FlatList
  ref='flatList'
  data={this.state.dataSource}
  extraData={this.state}
  enableEmptySections
  renderItem={({item}) => item.display && <Text>{item.text}</Text>} />

It's a bit convoluted, but I believe extraData acts as a comparison to see if anything in that data set updates. If so, re-render. That problem is solved.

"undefined is not an object (evaluating 'this._listRef')" still not fixed

Update 3: Scroll function works IF taken outside the setTimeoutfunction

Update 4:

Setting the initial data array

  this.state = {
    dataSource: pageService(state.params.page),
    cursor: 0
  }

Setting display as true:

  renderNextModule() {
    let newCursor = this.state.cursor
    if (newCursor < this.state.dataSource.length-1) {

        newCursor++
        let newSource = this.state.dataSource
        newSource[newCursor].display = true
        this.setState({
            dataSource: newSource,
            cursor: newCursor
        })
    }
  }
Organiccat
  • 5,633
  • 17
  • 57
  • 104

3 Answers3

4

this._listRef is undefined because setTimeout calls the function and sets its context (this) to null.

As a workaround, you can actually use a wrapper function (here with ES6 syntax) :

setTimeout(() => this.refs.flatList.scrollToEnd(), 0)

Regarding your 1st problem, I don't think you're solving it the right way. You were right that it uses a shallow comparison, but you might not need the extraData prop.

Are you mutating your array directly? How are you updating the item.display property?


For your 1st problem,
Replace this line

let newSource = this.state.dataSource

By this

let newSource = this.state.dataSource.slice()

In JavaScript, arrays are copied by reference, which means that you were not cloning the array but mutating it ; and as FlatList is efficient (and this is for every PureComponent), when you want to re-render it (by setting the state like you did), it does a === comparison on its old and new props to see if anything changed.

As you were mutating the old array, 'oldArray === newArray', so it was deciding to not update again.

This was a bit concise and fast but you can read more about why you need to create a new array with .slice() here. This is true for arrays and objects in JavaScript.

yachaka
  • 5,429
  • 1
  • 23
  • 35
  • The array is a state value, so I'm using setState to change it. The initial value is pulled from another function "pageData(pageNumber)" with a return. I update the item.display property via a setState as well. I will update with code – Organiccat Jun 08 '17 at 01:05
  • I had forgotten to bind my scrollToBottom...which fixes the this problem this.scrollToBottom = this.scrollToBottom.bind(this) Still interested in hearing about how you might re-render the area though (if you see a problem in the solution above) – Organiccat Jun 08 '17 at 01:08
  • Added a solution and explanation :) – yachaka Jun 08 '17 at 01:18
  • (you can hopefully remove the `extraData` prop, now) – yachaka Jun 08 '17 at 01:19
  • Thanks, I will test that out! I thought react did some magic and returned the value, rather that passing the reference – Organiccat Jun 08 '17 at 01:26
  • Sure :) please note that passing the reference is the JavaScript behavior, it's not directly related to React – yachaka Jun 08 '17 at 01:27
  • This does work, I don't need extraData for comparison anymore – Organiccat Jun 08 '17 at 13:00
  • Anybody have any idea why the setTimeout is necessary? My list does not scroll predictably, If the time is less than a second it does not react the end of the list and stops in random places. – xerotolerant Aug 22 '17 at 01:53
2

Its work for me

data={this.state.messages}
ref = "flatList"
onContentSizeChange={()=> this.refs.flatList.scrollToEnd()}
Surender Kumar
  • 1,152
  • 16
  • 17
0

in case of function component.

let flatList = React.useRef(null);

and in your FlatList component

<FlatList
  ref={flatList}
  onContentSizeChange={()=> flatList.current.scrollToEnd()}
  ....
 />
samehanwar
  • 3,280
  • 2
  • 23
  • 26