Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FlatList] render issues using scrollToIndex on componentDidMount #13202

Closed
conrad-vanl opened this issue Mar 29, 2017 · 44 comments
Closed

[FlatList] render issues using scrollToIndex on componentDidMount #13202

conrad-vanl opened this issue Mar 29, 2017 · 44 comments
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@conrad-vanl
Copy link

conrad-vanl commented Mar 29, 2017

Description

Running into an inconsistent bug when trying to use scrollToIndex on componentDidMount. Essentially, if the index I'm scrolling to is outside of the initialNumToRender, it doesn't get rendered until I start manually scrolling. You can see the behavior in this gif:


(apologies, just realized it is nearly 40mb in size....)

I have tracked the error down to the onScroll handler being called with inaccurate data:

image

Which sets the visibleLength and contentLength properties to zero.

This appears that it could be a problem with ScrollView itself, but I'm not sure. As soon as I start manually scrolling, the onScroll handler fires with the correct properties, and the view content renders.

Reproduction Steps and Sample Code

I couldn't get sketch.expo to work, but this example reproduces the issue for me on a fresh React-Native project: https://gist.github.com/conrad-vanl/41e2abb244d0b6e1bd952bd4ff4b3cd7

Essentially:

const style = {
  justifyContent: 'center',
  alignItems: 'center',
  height: 100,
  margin: 25,
  borderWidth: 1,
  borderColor: 'black',
};

class ScrollToExample extends Component {
  componentDidMount() {
    this.list.scrollToIndex({ index: this.props.scrollToIndex || 0 });
  }

  getItemLayout = (data, index) => (
    { length: 150, offset: 150 * index, index }
  );

  render() {
    return (
      <FlatList
        onScroll={(e) => { console.log('onScroll', e.nativeEvent); }}
        style={{ flex: 1 }}
        ref={(ref) => { this.list = ref; }}
        keyExtractor={item => item}
        getItemLayout={this.getItemLayout}
        renderItem={({ item }) => (
          <View style={style}><Text>{item}</Text></View>
        )}
        {...this.props}
      />
    );
  }
}

export default function() {
  return (
    <ScrollToExample
      data={longList}
      scrollToIndex={50}
    />
  );
}

And I also have the repo that produced the above GIF as well: https://github.com/conrad-vanl/rn-flat-list-tests

Solution

None yet. Finally confident that I'm reproducing the issue consistently enough. I believe it might be an issue with ScrollView but I'm not familiar enough (yet) with the intricacies here, so figured I'd post the issue while I continue to track issue origin.

Additional Information

  • React Native version: 0.43.0-rc.4
  • Platform: iOS; unconfirmed on Android
  • Development Operating System: MacOS
  • Dev tools: Xcode 8.2.1
@joshjhargreaves
Copy link
Contributor

joshjhargreaves commented Apr 3, 2017

👍 (plus 1) from me. I'd agree, the issue does seems to lie with trying to scroll to an index/contentOffset outside the initial render window.

Given the 'initialNumToRender' renders the items starting in 0th index in the provided array of items. As a user of the component, for example, would it make any sense to be abled to specify the 'initialNumToRender' as well as the starting index of the items to render and either the component can manage the appropriate starting contentOffset or this can be done externally? @sahrens mentioned that the initialNumToRender was a pull to refresh optimisation, so I'm not sure.

@conrad-vanl : interesting find with the onScroll event.

@joshjhargreaves
Copy link
Contributor

@conrad-vanl: the reason your example wasn't working in sketch is that FlatList is in pre-release in 0.43 and AFAIK sketch is currently bundling 0.42.

@conrad-vanl
Copy link
Author

would it make any sense to be abled to specify the 'initialNumToRender' as well as the starting index of the items to render and either the component can manage the appropriate starting contentOffset or this can be done externally?

Not sure. The bug here definitely has to do with the bad values being sent in the onScroll event though.

@joshjhargreaves
Copy link
Contributor

The behavior I'm seeing is blank content loaded initially.
Scrolling backwards the content is blank until reaching the initially rendered pages. Scrolling forwards again, the content is rendered as expected.

@conrad-vanl
Copy link
Author

conrad-vanl commented Apr 3, 2017

@joshyhargreaves I came across that issue as well. For me, it had to do with the InteractionManager.runAfterInteractions method that is used by FlatList VirtualizedList. Essentially, I had some Animations that weren't being properly "stopped" or torn down...which was causing the exact same behavior you mention (blank items until I reach the items that initially rendered).

Potentially related to #8624

@joshjhargreaves
Copy link
Contributor

joshjhargreaves commented Apr 3, 2017

@conrad-vanl funnily enough, it looks like runAfterInteractions isn't being called for myself (in relation to the link you posted).

Just out of interest, if you call scrollToIndex with a setTimeout with some arbitrary period of 2s, say, does the problem still occur for you?

@conrad-vanl
Copy link
Author

it looks like runAfterInteractions isn't being called for myself (in relation to the link you posted).

@joshyhargreaves no, but VirtualizedList uses it under the hood. Cell updates in FlatList are batched and run after interactions....if the InteractionManager gets hung up (like in #8624) it'll create the issue you describe.

Will have to check on your other Q

@joshjhargreaves
Copy link
Contributor

@conrad-vanl thanks for clarifying, much appreciated.

@sahrens
Copy link
Contributor

sahrens commented Apr 3, 2017

Yes I think it makes sense to add some initialScrollIndex or something, might add that soon.

Are you providing getItemLayout?

There should be logic to promote the cell rendering priority to bypass runAfterInteractions if they are close enough to or inside the viewport - perhaps that logic is broken? Or you are just doing other heavy JS computation that's blocking it up?

@sahrens
Copy link
Contributor

sahrens commented Apr 3, 2017

Oh sorry, looks like you are providing getItemLayout. I'll take a look.

@conrad-vanl
Copy link
Author

conrad-vanl commented Apr 3, 2017

@sahrens the runAfterInteractions bit I believe should be a separate issue, but yes, there was other JS computation that was blocking (specifically a looping Animation that wasn't being properly stopped). The only weird part was that once you scroll to the top to the rows that were rendered initially, the list snapped back into working it would start rendering cells as you scrolled down.

Again, that's a different issue that was fixed (for me, atleast) by making sure runAfterInteractions is properly firing. This issue is purely about trying to use scrollToIndex on initial mount not working, I believe based on the onScroll handler not reporting correct values (console.log onScroll to see what I mean, like my code example above).

@joshjhargreaves
Copy link
Contributor

joshjhargreaves commented Apr 3, 2017

Yep, does look like a couple of seperate issues!

@sahrens it looks like I've got a separate problem with runAfterInteractions being locked up which I'll have to get to the route of why. However, from the looks of it (or at least from what I can see), it looks like when using scrollToIndex (or the scrollViewcontentOffset prop) to adjust the viewPort/contentOffset programmatically the cell rendering priority is not promoted to bypass runAfterInteractions until the initialNumToRender items are back in view.

@joshjhargreaves
Copy link
Contributor

@conrad-vanl: thanks for the heads up about InteractionManager. Turns out we had a looping loading spinner animation that was blocking the Interaction Manager; fixed by specifying isInteraction to be false when configuring the animations.

@sahrens fixing the InteractionManager block actually fixes the issue with blank content, and cells are now rendering, given runAfterInteractions is now being called :). These cells at the arbitrary offset are not rendered with the same priority as initialNumToRender, as we'd expect perhaps.

So I think you're right, as a result of these items being rendered with a low priority not high I think`initialScrollIndex' would make a lot of sense :).

I'd be interested to see what the logic would might look like, so will have a look out of interest!

Thanks a lot.

@joshjhargreaves
Copy link
Contributor

@conrad-vanl I'm actually seeing the same issue as you, although intermittently.

@joonhocho
Copy link
Contributor

@conrad-vanl #13316 Even the most basic example has the same problem.

@ludovicjamet
Copy link

ludovicjamet commented Apr 19, 2017

I got this problem to, I found a little hack to avoid this issue until it's fixed. Just add a sleep() like function before scrollToIndex like

componentDidMount() {
  let wait = new Promise((resolve) => setTimeout(resolve, 500));  // Smaller number should work
  wait.then( () => {
    this.refs.flatlist.scrollToIndex({index: 4, animated: true});
  });
}

@irrigator
Copy link

irrigator commented Apr 20, 2017

@ludovic-coder Your solution works! The only caveat is that initialNumToRender becomes useless and better to be set to 0.

However, the support for a initialScrollIndex prop is definitely better.

@ludovicjamet
Copy link

@irrigator Definitely, initialScrollIndex is a better choice.

@hramos hramos changed the title FlatList: render issues using scrollToIndex on componentDidMount [FlatList] render issues using scrollToIndex on componentDidMount Apr 25, 2017
@joshjhargreaves
Copy link
Contributor

joshjhargreaves commented Apr 25, 2017

@irrigator additionally, using scrollToIndex in this fashion means that the content we are trying to render initially at this given offset will not be rendered with the same priority as the content with initialNumToRender. If there are any ongoing interactions, our items at this offset will be rendered after the Interaction(s) have taken place whereas initialNumToRender are not batched in this fashion and will render straight away.
edit: at least this has been my observation and seems to be true from the VirtualizedList source.

@irrigator
Copy link

@joshyhargreaves You are right. And that's why this approach is only a workaround. Also, you do need to put the scrollToIndex call in a timeout like @ludovic-coder's code.

@sahrens
Copy link
Contributor

sahrens commented Apr 26, 2017

https://github.com/facebook/react-native/blob/master/Libraries/Lists/FlatList.js#L125

@sahrens sahrens closed this as completed Apr 26, 2017
@joshjhargreaves
Copy link
Contributor

Thank you @sahrens

@leonidkuznetsov18
Copy link

How to upgrade react-native to master branch version? Because only in master include this method initialScrollIndex

@joshjhargreaves
Copy link
Contributor

joshjhargreaves commented Apr 28, 2017

@MrChe you can point your package.json react-native dependency at facebook/react-native if you want latest master. You can also tie to a particular commit facebook/react-native#commitHash which is definitely recommended.

Another option is pulling FlatList.js and VirtualizedList.js from RN master into your project and import them with a relative file import. N.B. you'd need to modify FlatList to import your local version of VirtualizedList. I.E. change require('VirtualizedList') to a local import or modify the @providesModule comment at the top of VirtualizedList to give it another alias to require in your copy of FlatList.

@mnkang89
Copy link

mnkang89 commented Apr 30, 2017

@sahrens First of all I appreciate for your work(initialScrollIndex) sahren, but I still got white space till i manually scroll the contents.. i'm using React Native Router Flux on my app. Is it related with navigation library that I'm using? and i also set false RemoveClippedSubviews that you suggested as a solution in other issues about rendering bug.

@joshjhargreaves
Copy link
Contributor

joshjhargreaves commented Apr 30, 2017

@mnkang89 @sahrens I am actually also seeing the exact same behaviour with React Native router flux and initialScrollIndex. I'm hoping to be able to push a project reproducing the issue in the next couple of days. Unless @mnkang89 you perhaps beat me to it? A repro project from anybody would be a big help! I wonder if we would see this issue with other JS navigators too; I would guess it's not specific to router flux.

@mnkang89, have you tried setting removeClippedSubviews to false? Unfortunately for me the problem still persists.
Edit: sorry, missed that you had already specified!

@mnkang89
Copy link

@joshyhargreaves yes i also tried setting removeClippedSubviews to false but i still got white space..

@sahrens
Copy link
Contributor

sahrens commented May 1, 2017

Weird, sounds like something specific to react native flux router...perhaps the layout and visibleHeight measurement are getting messed up? Can you try logging the scrollMetrics everywhere they are updated in VirtualizedList along with the function that updates it, and log when the scrollTo call is made, and let me know what's printed?

Also, can you try increasing the timeout for initialScrollIndex and see if that fixes it? Try maybe 17ms and 200ms.

@joshjhargreaves
Copy link
Contributor

Annoyingly the 'blank content until scroll' seems to happen sporadically 'come and go' a few times in succession and not again for a while (I'm not really sure what the varying factors causing it, for me at least!). I haven't managed to recreate it in the last hour or so. But here are are the scrollMetrics for the different setTimeout values.

setTimeout values

the tl:dr is that with a setTimeout of 0 the scrollMetric visibleLength is 0 when calling scrollTo in componentDidMount, whereas with a callback time of 17ms or greater the visibleLength is correct. This is due to the onLayout being called after the callback when the timout is 0 and after the cb when the setTimeout time is greater.
This may just be the expected behavior? I will try and see what these values are when the 'blank content' does occur and if I can confirm that setting the timeout to be greater does fix the issue from occurring. @mnkang89, perhaps you could also log scrollMetrics in virtualizedList?

I have also created a separate project with navigator-flux and flatList. So can push that if that's any help; I do seem to be seeing the blank content with the same frequency as the production app with this project (but the occurrence is fairly sporadic).

@sahrens
Copy link
Contributor

sahrens commented May 1, 2017

I probably should have just set it up to wait for onLayout instead of doing the setTimeout.

You said that visibleLength is correct with a value of 17 - does everything render correctly as well in that case? Or are there still bugs?

@joshjhargreaves
Copy link
Contributor

Damn, it does look like a consistent reproducible example would be super useful at this point. I remember seeing a couple of issues where people had posted some examples that could be helpful, I'll test a few out and post back with anything interesting. I guess we are still speculating slightly at this point too!

With 17ms, as far as I can tell, no visual defects; this is in a horizontal viewPager like scenario.

@mnkang89
Copy link

mnkang89 commented May 2, 2017

@sahrens I totally agree with you. I think it is more solid to set it up to wait for onlayout rather than to use setTimeout.

@joshjhargreaves
Copy link
Contributor

@mnkang89 just to sanity-check, does adding a timeout of 17ms to VirtualizedList fix the issue for you?

@mnkang89
Copy link

mnkang89 commented May 2, 2017

@joshyhargreaves yes I've set timeout of 17ms in virtualizedList like below. and I also tried 200ms but it doesn't work. i sill got blank space.

FYI I attached gif file of my app's current situation

<17ms in VirtualizedList>

2017-05-02 19 27 36

rnblankspacegif

@joshjhargreaves
Copy link
Contributor

@mnkang89 thank you for that.

Hmm, that is interesting. What can we say about the order in which onLayout is being called here, do you still think it's still being called after the componentDidMount 'scrollTo' callback?

@mnkang89
Copy link

mnkang89 commented May 2, 2017

@joshyhargreaves No problem

well I've tried logging scrollmetrics just now.
weired but.. visibleLength for blank content is correct.
I think it could be not related with the order in which onLayout is called after callback. and I also set settimeout value to 2000(2s), 10000(10s) but I still got blank content.

hmm....am I doing something wrong?

my code and result were like below.
2017-05-02 23 06 49
2017-05-02 23 07 01

@joshjhargreaves
Copy link
Contributor

@mnkang89, as far as I'm aware, there's a race condition when printing out objects in memory in the chrome console (if they're updated soon after the console.log) itself.
screen shot 2017-05-02 at 3 23 39 pm.
See how contentLength is different when expanded.

You can either JSON.stringify(this._scrollMetrics), or look at the logs in console in XCode.

Regarding the timeout values, I'm not very sure myself, hopefully @sahrens might have some more insight :S.

@joshjhargreaves
Copy link
Contributor

joshjhargreaves commented May 3, 2017

@mnkang89, for some reason your comment isn't listed here?

it works, I just only set removeclippedsubviews for horizontal flatlist. but after I set removeclippedsubviews for vertical one too It works like a charm!

Wow, I may have made a bit of an oversight too. I have scrollViews a horizontal FlatList and your experience would suggest that they should have removeClippedSubviews set to false too.

I think there's a lot to be said about using a pure native navigation solution at this point assuming this is part of the issue. facepalm

@leonidkuznetsov18
Copy link

Sorry to interfere. I have a very serious problem with FlatList. When the coup is wrong, the calculation is up to the element, here is an example video - http://take.ms/DHdyO
And this my issue - #13736
I would be grateful if you could help me.

@mnkang89
Copy link

mnkang89 commented May 3, 2017

@joshyhargreaves yeah actually I deleted my comment by mistake lol.. anyway set both of flatlist's removeclippedsubviews to false does work for me haha. I'm not sure it is only navigation problem. cause I saw similar issues in react-navigation also. like these react-navigation/react-navigation#1338

@sahrens
Copy link
Contributor

sahrens commented May 3, 2017

removeClippedSubviews is now (in master and future v0.45) false by default in VirtualizedList to avoid these issues. If you need more perfs, you can enable it on an opt-in basis at your own risk :P

@ariona
Copy link

ariona commented May 30, 2017

Hi @sahrens, any advice for navigating the FlatList that have variable height?

@Kiarash-Z
Copy link

Kiarash-Z commented Jun 14, 2017

Hi mine fixed by giving style={{ backgroundColor: 'white' }} to flat list

johanbissemattsson added a commit to johanbissemattsson/paperless that referenced this issue Oct 2, 2017
…Index not rendering first item until user scroll (see description)

Follow issue here: facebook/react-native#13202

initial state of scrollindex can be removed, as well as the timeout + ref
@dann1609
Copy link

why this is closed? This solution do not solve the problem!!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests