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

onEndReached triggers infinitely when unmounting FlashList #609

Closed
bencreynolds opened this issue Sep 21, 2022 · 8 comments
Closed

onEndReached triggers infinitely when unmounting FlashList #609

bencreynolds opened this issue Sep 21, 2022 · 8 comments
Labels
bug Something isn't working

Comments

@bencreynolds
Copy link

bencreynolds commented Sep 21, 2022

Current behavior

<FlashList 
                    contentContainerStyle={{paddingTop: 10}}
                    data={data}
                    renderItem={({item, index}) => <FeedItem item={item} index={index} />}
                    estimatedItemSize={windowWidth + 110}
                    showsVerticalScrollIndicator={false}
                    onEndReachedThreshold={2}
                    onEndReached={() => getMoreData()}
/>

When I navigate away from this screen (using React Navigation), the onEndReached function is called infinitely until it finally runs out of new data to append. to the data array. I am using this function to have infinite scroll. I assume this behavior is because when the FlatList items are unmounted, their heights are set to 0, which causes the onEndReached function to continuously trigger since it instantly hits the end.

Expected behavior

When I navigate away from the FlashList (using react navigation), the FlashList should unmount immediately and stop executing any functions.

To Reproduce

Make a FlashList with infinite scroll (Like in the Twitter example) and then navigate to a different screen using React Navigation. Add a console.log() to the renderItem that triggers on render. When you navigate away, it will trigger a console log for every single data item, even items that weren't yet loaded in.

Platform:

Web,

I have no yet tested this behavior on iOS or Android.

Environment

"@shopify/flash-list": "1.2.2",

@bencreynolds bencreynolds added the bug Something isn't working label Sep 21, 2022
@naqvitalha
Copy link
Collaborator

Doesn't happen in our sample app. Can you share a sample where we can reproduce this?

@TaiwoO
Copy link

TaiwoO commented Nov 16, 2022

The issue seems to be triggered when conditionally rendering the FlashList. I resolved it by wrapping it in a View and using display:none instead of conditional render.

@fortmarek
Copy link
Contributor

Closing. Please, open a new issue with full reproduction steps in our sample app or snack.

@tlmader
Copy link

tlmader commented Nov 7, 2023

Running into this issue as well. After navigating to another screen, onEndReached is called infinitely.

@kilbot
Copy link

kilbot commented Nov 5, 2024

I have recently run into this issue. I will leave my solution here in case it helps others:

Navigating away from a screen in react-natigation will keep the screen components mounted, but all layout sizes will go to 0. This triggers the onEndReached function - and for me a FlashList's rendered size is not usable. warning.

Two solutions:

  1. use the unmountOnBlur prop on the DrawerNavigator, or
  2. use the useFocusEffect hook to conditionally render the FlashList, eg:
const [isFocused, setIsFocused] = React.useState(false);
useFocusEffect(
	React.useCallback(() => {
		setIsFocused(true);
		return () => setIsFocused(false);
	}, [])
);

@Vimiso
Copy link

Vimiso commented Nov 7, 2024

Shouldn't this be managed in the package itself? It seems to me if we move away from the screen (component unmounts) onEndReached shouldn't be triggered, no matter the screen height. I suppose in order to have a solid solution you need to consider what subsequently happens when reentering the screen, especially if wanting to preserve what was previously there without reloading.

@kilbot
Copy link

kilbot commented Nov 7, 2024

if we move away from the screen (component unmounts)

The screen does not unmount in react-navigation, unless you explicitly use the unmountOnBlur prop.

But you are correct, any solution that unmounts the FlashList is going to introduce other problems. At the end of the day, I don't think FlashList is a good fit for react-native-web and react-navigation applications. There are other libraries that are better suited for the web, and it's fairly easy to import different components for different platforms nowadays.

@Vimiso
Copy link

Vimiso commented Nov 7, 2024

Yeah agreed. The problem I found was wanting to leave the list rendered, while I navigate to other tabs, and come back to it exactly how I left it. Well, because navigating away triggers onEndReached my fetchMoreItems callback was pulling in items from the next batch, causing the list to have changed when navigating back! My current solution is the following:

const [data, setData] = useState(items)
const [isLoading, setIsLoading] = useState(true)

const loadItems = useCallback((lastKey = null, action) => {
  console.log('load_items', action)

  setIsLoading(true)

  return request('get', `/api/list?limit=${limit}&last_key=${lastKey}`).then(response => {
    setData(prevData => [...prevData, ...response.data.results])
  }).catch(e => {
    // console.error(e)
  }).finally(() => {
    setIsLoading(false)
  })
}, [limit])

const fetchInitialItems = useCallback(() => {
  if (data.length > 0) {
    return Promise.resolve()
  }

  return loadItems(null, 'fetch_initial')
}, [data])

const fetchMoreItems = useCallback(() => {
  if (isLoading || data.length === 0) {
    return Promise.resolve()
  }

  const lastKey = data.length > 0 ? data[data.length - 1].published_key : null

  return loadItems(lastKey, 'fetch_more')
}, [isLoading, data])

// Fetch initial items only if no data...
useEffect(() => {
  if (data.length === 0) {
    fetchInitialItems()
  }
}, [data])

// Deal with focusing...
useFocusEffect(
  useCallback(() => {
    console.log('focus')

    // On focus set `isLoading` back to its default (`false`) so the user can continue loading more.
    setIsLoading(false)

    return () => {
      console.log('unfocus')

      // On unfocus set `isLoading` to `true` so that `fetchMoreItems` is not triggered.
      setIsLoading(true)
    }
  }, [])
)
<FlashList
  ref={ list }
  data={ data }
  keyExtractor={ item => item.uuid }
  renderItem={ renderItem }
  estimatedItemSize={ 100 }
  onEndReached={ fetchMoreItems }
  onEndReachedThreshold={ 0 }
  ListFooterComponent={ isLoading ? renderLoading : null }
/>

...feels sketchy, but does the job until I can figure out a better approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

7 participants