React Native FlatList onScroll Becomes Unresponsive with Large Data Sets in Expo App

I’m working on an Expo React Native app that has a header, footer, and an action button toggle that animates based on the scroll direction. The app has two tabs on the home page: “Social Posts” and “Rankings”. The “Social Posts” tab displays dynamic content loaded from an API, while the “Rankings” tab is static.

The issue I’m facing is with the “Social Posts” tab, which uses a FlatList to display a large amount of data. When the list reaches the end, it loads more data using onEndReached. The header, footer, and button animate out of view when scrolling down, which is working as expected. However, as the data grows larger, the onScroll event in the FlatList becomes unresponsive and does not fire consistently.

This inconsistency causes the header, footer, and button to get stuck in their last state and stop responding to scroll events. I’ve tried various performance optimizations, including using useMemo and useCallback on each list item, but the problem persists. The scroll experience becomes laggy and feels less smooth when there’s a large dataset.

Demo:

I’ve created a Snack demo that replicates the issue. You can find the complete code and see the problem in action by visiting the demo.

Here is a simplified version of my code:

import React, { useState, useEffect, useCallback } from 'react';
import { View, Text, FlatList, StyleSheet, ActivityIndicator, Image } from 'react-native';
import throttle from 'lodash.throttle';
import useWindowScrollStore from '../store/useWindowScrollStore';

function SocialFeed() {
  const [data, setData] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);

  // Fetch data from the DummyJSON API
  const fetchData = async () => {
    if (loading) return;

    setLoading(true);
    try {
      const response = await fetch(`https://dummyjson.com/products?limit=10&skip=${(page - 1) * 10}`);
      const result = await response.json();

      if (result.products.length > 0) {
        setData((prevData) => [...prevData, ...result.products]);
        setPage(page + 1);
      } else {
        setHasMore(false);
      }
    } catch (error) {
      console.error('Error fetching data:', error);
    } finally {
      setLoading(false);
    }
  };

  // Initial fetch
  useEffect(() => {
    fetchData();
  }, []);

  const onListScroll = useCallback(
    throttle((nativeEvent) => {
      useWindowScrollStore.getState().setScrollY(nativeEvent.contentOffset.y);
    }, 100),
    []
  );

  const renderItem = ({ item }) => (
    <View style={styles.postContainer}>
      <Image source={{ uri: item.thumbnail }} style={styles.image} />
      <Text style={styles.postText}>{item.title}</Text>
      <Text style={styles.descriptionText}>{item.description}</Text>
    </View>
  );

  const renderFooter = () => {
    if (!loading) return null;
    return <ActivityIndicator size="large" color="#0000ff" />;
  };

  return (
    <View style={styles.container}>
      <FlatList
        data={data}
        renderItem={renderItem}
        keyExtractor={(item) => item.id.toString()}
        onScroll={({ nativeEvent }) => onListScroll(nativeEvent)}
        onEndReached={hasMore ? fetchData : null}
        onEndReachedThreshold={0.5}
        ListFooterComponent={renderFooter}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 10,
  },
  postContainer: {
    backgroundColor: '#f0f0f0',
    padding: 15,
    marginVertical: 8,
    borderRadius: 10,
  },
  postText: {
    fontSize: 16,
    color: '#333',
    fontWeight: 'bold',
  },
  descriptionText: {
    fontSize: 14,
    color: '#666',
    marginTop: 5,
  },
  image: {
    width: '100%',
    height: 200,
    borderRadius: 10,
    marginBottom: 10,
  },
});

export default SocialFeed;

What I’ve Tried:

  • Throttling the onScroll event using lodash.throttle.
  • Using useMemo and useCallback to optimize the rendering of list items.
  • Implementing performance improvements by reducing re-renders and optimizing state updates.

The Problem:

Despite these optimizations, the onScroll event still lags or stops firing correctly when the list becomes large. This issue affects the header, footer, and action button toggle functionality, making them unresponsive to scroll events.

Question:

How can I improve the responsiveness of the onScroll event in a FlatList with large datasets? Are there other optimization techniques I should consider for better performance and a smoother scroll experience?

Any advice or suggestions would be greatly appreciated!