Shopify / flash-list

A better list for React Native
https://shopify.github.io/flash-list/
MIT License
5.49k stars 282 forks source link

Inconsistent contentSize/contentOffset between renders #943

Open stevengoldberg opened 11 months ago

stevengoldberg commented 11 months ago

Current behavior

I have a large, multi-column vertical list (1k+ items, 7 columns). The size of the cells is constant, as is the number of cells the list renders. I have some behavior that depends on the list's current scrollOffset. e.g.:

const updateScroll = ({ nativeEvent }) => {
  // keep track of nativeEvent.contentOffset.y
  // or nativeEvent.contentSize.height
}

return (
  <FlashList
    data={data}
    estimatedItemSize={ITEM_HEIGHT}
    onMomentumScrollEnd={updateScroll}
    onDragScrollEnd={updateScroll}
  />
)

On first render, everything works as expected. I scroll around the list and the reported contentSize doesn't change.

But when I cause a re-render by changing a value in one of the existing cells — which doesn't change the size of any cells or change the number of cells rendered — contentSize.height becomes slightly smaller.

The discrepancy isn't constant, but varies somehow with the size of the list. Additionally, when I scroll around the list in this state, contentSize.height will change by very small amounts.

If I reload the app in this state, contentSize.height goes back to its original value and everything works as expected until I update the data again.

Expected behavior

I expect contentSize.contentOffset.y to stay the same between the renders, and as I scroll up and down the list.

To Reproduce

This only seems to occur with a list large enough that I can't scroll through it without hitting blank cells. At smaller list sizes the problem doesn't occur. I'm seeing it with contentSize.y around 23000.

Platform:

Environment

1.6.2

stevengoldberg commented 11 months ago

After more investigation I think I found the source of this problem — not sure if this is a bug, or a documentation issue, or user error on my part.

My component is a calendar, so it's a 7-column layout for the day cells, and then at the top of each month it has a full-width section header.

I was using overrideItemLayout like this:

const overrideItemLayout = (layout, item) => {
        if (item.type === 'header') {
            layout.span = 7
            layout.size = HEADER_HEIGHT
        } 
    }

That caused the behavior above, where the list size was calculated correctly on the first render, but incorrectly on subsequent renders. When I change it to this, the issue no longer occurs, and the list size stays the same:

const overrideItemLayout = (layout, item) => {
        if (item.type === 'header') {
            layout.span = 7
            layout.size = HEADER_HEIGHT
        } else {
            layout.span = 1
            layout.size = dayHeight
        }
    }
stevengoldberg commented 11 months ago

I created a minimal repro of this issue here: https://snack.expo.dev/@yungchomsky/tenacious-red-celery

To observe:

  1. Load the example in iOS
  2. Scroll around the list manually; the size of the scroll content is logged as 111925
  3. Press the Set Data button to change the value of the first cell
  4. Scroll around the list again; the scroll content size is now changed
  5. Press the Scroll to End button, then manually scroll again; the scroll content size has changed again

If you uncomment the second condition inside overrideItemLayout, the scroll content will always remain at 111925.

roots-ai commented 2 months ago

Resolution?

stevengoldberg commented 2 months ago

Resolution?

The workaround described here has been fine for me https://github.com/Shopify/flash-list/issues/943#issuecomment-1805033256

ajitaro commented 1 month ago

Thank you it works for me