React Native (RN): Pagination with FlatList (Infinite Scrolling)

React Native (RN) Pagination with FlatList (Infinite Scrolling) | TheDevDigest

Mobile apps often deal with large lists of data, whether it's social media feeds, product catalogs, or search results. Displaying all this information at once leads to slow loading times, poor performance, and a frustrating user experience. This is where pagination comes in – a technique that loads and displays data in chunks, making your app feel smooth and responsive.

This article will explore implementing pagination in React Native (RN) using the versatile FlatList component. We'll use the https://jsonplaceholder.typicode.com/photos API to display a paginated gallery of images, focusing on handling the case when we reach the end of the data correctly.

Why Pagination Matters

Imagine scrolling through thousands of products in an e-commerce app. Without pagination, the app would try to load everything simultaneously, causing:

  • Slow initial loading: Users face a blank screen while the app fetches and renders the entire dataset.
  • Performance issues: Scrolling becomes laggy, especially on less powerful devices.
  • Increased data usage: Downloading unnecessary data wastes bandwidth.

Pagination solves these problems by:

  • Fetching data in manageable chunks: Only the initial items are loaded, improving loading times.
  • Optimizing rendering: The app renders a limited number of items at a time, ensuring smooth scrolling.
  • Reducing data usage: Data is loaded on demand, conserving bandwidth.

Implementing Pagination with FlatList in RN

React Native's FlatList is excellent for building performant lists with built-in pagination support. Let's break down the process:

1. Setting up the Project:

If you haven't already, create a new React Native project:

npx expo init MyPaginationApp
cd MyPaginationApp

2. Building the FlatList with Accurate End-of-Data Handling:

Here's our App.js file with comments explaining the pagination logic:

import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, StyleSheet, Image, ActivityIndicator } from 'react-native';

const App = () => {
  const limit = 20;
  const [photos, setPhotos] = useState([]); // Store fetched photos
  const [page, setPage] = useState(1); // Current page number
  const [isLoading, setIsLoading] = useState(false); // Loading state
  const [isEndOfData, setIsEndOfData] = useState(false); // Flag for end of data

  useEffect(() => {
    fetchPhotos();
  }, [page]); // Fetch photos whenever 'page' changes

  const fetchPhotos = async () => {
    if (isLoading || isEndOfData) return; // Don't fetch if already loading or at the end

    setIsLoading(true);
    try {
      console.log('page:', page);
      const response = await fetch(
        `https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=${limit}`
      );
      const data = await response.json();

      // Correct condition for end of data:
      if (data.length < limit) { 
        setIsEndOfData(true);
      } else {
        setPhotos(prevPhotos => [...prevPhotos, ...data]);
      }
    } catch (error) {
      console.error('Error fetching photos:', error);
    } finally {
      setIsLoading(false);
    }
  };

  const renderItem = ({ item }) => (
    <View style={styles.item}>
      <Image source={{ uri: item.thumbnailUrl }} style={styles.thumbnail} />
      <Text>{item.title}</Text>
    </View>
  );

  const handleLoadMore = () => {
    if (!isLoading) {
      setPage(page + 1); // Increment page to load next set
    }
  };

  const renderFooter = () => {
    if (isEndOfData) {
      return (
        <View style={styles.endMessage}>
          <Text>End of Gallery</Text>
        </View>
      );
    } else if (isLoading) {
      return (
        <View style={styles.loader}>
          <ActivityIndicator size="large" />
        </View>
      );
    } else {
      return null;
    }
  };

  return (
    <View style={styles.container}>
      <FlatList
        data={photos}
        renderItem={renderItem}
        keyExtractor={item => item.id.toString()}
        onEndReached={handleLoadMore}
        onEndReachedThreshold={0.5}
        ListFooterComponent={renderFooter} // Render footer
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  item: {
    marginVertical: 8,
    alignItems: 'center'
  },
  thumbnail: {
    width: 100,
    height: 100,
    marginBottom: 5,
  },
});

export default App;

3. Running the App:

Start your React Native application:

npm run android // For Android
npm run ios // For iOS

Now you have a robustly paginated gallery that accurately handles the end of the data, providing a smooth and user-friendly experience.



Comments