FlatList onEndReached not working properly

264 Views Asked by At

I have a simple component with list of Cards, what I want to do is also simple which is infinite scrolling, so that when I reach almost the end to just increment the page by 1 and call my API ( using RTK query here ) but the onEndReached have a very weird behaviour.

It get called like 10 times and when I check my API calls page is 2,4,5,6,8, 10... So it's like also skipping pages and it's calling these when I scroll ONLy one time to the end.

Here is my component

import { FlatList, StyleSheet, View } from 'react-native';
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';

import { useGetNearbyPlacesQuery } from '@redux/queries/generalAPI';
import StoreCardSkeleton from '@components/StoreCard/CardSkeleton';
import StoreCard from '@components/StoreCard';
import { hp } from '@utils/responsive';
import ListLayout from '@components/ListLayout';
import { selectSessionLocation } from '@redux/reducers/authSlice';

const LIMIT = 10;

const Places = ({ route }) => {
  const category = route?.params?.category;
  const { t } = useTranslation();
  const [page, setPage] = useState(1);
  const sessionLocation = useSelector(selectSessionLocation);
  const {
    data = [],
    isFetching = true,
    refetch,
  } = useGetNearbyPlacesQuery({
    lat: sessionLocation?.[1],
    lng: sessionLocation?.[0],
    page,
    limit: LIMIT,
    type: category,
  });

  const renderItem = useCallback(
    ({ item }) => (
      <View style={styles.item}>
        <StoreCard
          logo={item?.logo}
          location={item?.buildingName}
          typeOfSector={item?.typeOfSector}
          storeId={item._id}
          name={item?.vendorName}
        />
      </View>
    ),
    []
  );

  const renderSkeletons = useCallback(() => {
    if (!isFetching) {
      return null;
    }
    const skeletonArray = Array.from({ length: 10 });
    return skeletonArray.map((_, index) => (
      <View style={styles.item} key={index}>
        <StoreCardSkeleton />
      </View>
    ));
  }, []);

  const handleRefresh = () => {
    if (page > 1) {
      setPage(1);
    } else {
      refetch();
    }
  };

  const handleEndReached = useCallback(() => {
    if (!isFetching) {
      setPage((prev) => prev + 1);
    }
  }, [isFetching]);

  return (
    <ListLayout
      title={t('nearby_places')}
      onRefresh={handleRefresh}
      renderLoading={renderSkeletons}
      onEndReached={handleEndReached}
      onEndReachedThreshold={0.2}
      loading={isFetching}
      data={data}
      renderItem={renderItem}
      keyExtractor={(item) => item?._id}
    />
  );
};

export default Places;

const styles = StyleSheet.create({
  item: {
    marginBottom: hp(20),
  },
});

and here is my Custom List Component

import {
  RefreshControl,
  StyleSheet,
  FlatList,
  ActivityIndicator,
  View,
} from 'react-native';
import React from 'react';

import EmptyResult from '@components/EmptyResult';
import Colors from '@constants/Colors';
import { hp } from '@utils/responsive';

const List = ({
  data = [],
  keyExtractor,
  renderItem = () => {},
  listStyle,
  loading = false,
  renderLoading = () => {},
  onEndReached = () => {},
  onRefresh = () => {},
  onEndReachedThreshold = 0.2,
  page,
}) => {
  const renderEmptyList = () => {
    if (loading) {
      return null;
    }
    return <EmptyResult />;
  };

  const defaultKeyExtractor = (item, index) => item._id + index.toString();

  const handleLoading = () => {
    if (page === 1) {
      renderLoading();
    } else if (loading) {
      return (
        <View style={styles.loadingWrapper}>
          <ActivityIndicator size="small" color={Colors.RED} />
        </View>
      );
    }
  };

  return (
    <FlatList
      data={data}
      keyExtractor={keyExtractor || defaultKeyExtractor}
      renderItem={renderItem}
      showsVerticalScrollIndicator={false}
      contentContainerStyle={[styles.list, listStyle]}
      onEndReachedThreshold={onEndReachedThreshold}
      ListEmptyComponent={renderEmptyList}
      ListFooterComponent={handleLoading}
      onEndReached={onEndReached}
      extraData={data}
      refreshControl={
        <RefreshControl
          refreshing={loading}
          onRefresh={onRefresh}
          tintColor={Colors.RED}
          colors={[Colors.RED]}
        />
      }
    />
  );
};

export default React.memo(List);

const styles = StyleSheet.create({
  list: {
    flexGrow: 1,
    paddingBottom: hp(30),
  },
  loadingWrapper: {
    alignContent: 'center',
    justifyContent: 'center',
  },
});
1

There are 1 best solutions below

0
On

hey there once I also go through this kind of problem there are some problems in the problem is This can happen because the "onEndReached" event is triggered when the user scrolls to the end of the list, but the event might be fired multiple times as the scroll position stabilizes.

const handleEndReached = useCallback(() => {

  if (!isFetching) {
    setPage((prev) => {
      const nextPage = prev + 1;
      setIsFetching(true);
      fetchData(nextPage);
      return nextPage;
    });
  }
}, [isFetching]);

const fetchData = async (nextPage) => {
  try {
    // Perform your data fetching logic here, for example using an API call
    // Once the data is fetched, set isFetching back to false to allow further 
  } catch (error) {
  } finally {
    setIsFetching(false);
  }
};

another way :- you can also use redux state for page number then you track your page number perfectly and remember that use "useFocusEffect" so when you leave that screen then you can reset your "pagenumber state". https://reactnavigation.org/docs/function-after-focusing-screen/

best of luck