DEV Community

Ajmal Hasan
Ajmal Hasan

Posted on • Edited on

Implementing Pagination and Pull-to-Refresh in React Native with a Custom Hook

Implementing Pagination and Pull-to-Refresh in React Native with a Custom Hook

In modern mobile apps, pagination and pull-to-refresh are essential features for handling large datasets efficiently and enhancing user experience. Here, we’ll walk through creating a custom pagination hook in React Native, coupled with pull-to-refresh functionality, making it easy to handle data fetching and seamless loading.

Pagination Hook and Pull-to-Refresh Implementation

Step 1: Set Up Initial State and Import Dependencies

Start by setting up the structure of your initial data, state variables, and importing necessary dependencies.

Expected API Response Format:

Our API response format is expected to be structured as shown below. This helps in managing pagination, tracking total results, and keeping the current page status.

{
  data: [],
  totalResult: 0,
  status: true,
  pageNo: 1,
  totalPages: 1,
  perPage: 10,
}
Enter fullscreen mode Exit fullscreen mode

If your API format differs, ensure you modify the hook to adapt to your API response.

Step 2: Create the usePagination Hook

Let’s define the usePagination hook to manage pagination, loading, and pull-to-refresh states. This hook will fetch data from an API endpoint, keeping track of the current page, total results, and more.

usePagination.jsx

import React, { useState, useEffect, useCallback } from 'react';

const initialData = {
  data: [],
  totalResult: 0,
  status: true,
  pageNo: 1,
  totalPages: 1,
};

const usePagination = () => {
  const [initialLoader, setInitialLoader] = useState(true);
  const [data, setData] = useState(initialData.data);
  const [totalResult, setTotalResult] = useState(initialData.totalResult);
  const [pageNo, setPageNo] = useState(initialData.pageNo);
  const [totalPages, setTotalPages] = useState(initialData.totalPages);
  const [refreshing, setRefreshing] = useState(false);
  const [loadingMore, setLoadingMore] = useState(false);

  // Fetch data for a given page
  const fetchData = async (page, perPage = 10) => {
    try {
      const response = await fetch(`https://dummyjson.com/products?limit=${perPage}&skip=${page}`);
      const resultOld = await response.json();

      const result = {
        data: resultOld?.products,
        totalResult: resultOld?.total,
        status: true,
        pageNo: page,
        totalPages: Math.ceil(resultOld?.total / perPage) || 10,
      };

      if (result.status) {
        setData(page === 1 ? result.data : [...data, ...result.data]);
        setTotalResult(result.totalResult);
        setPageNo(result.pageNo);
        setTotalPages(result.totalPages);
      } else {
        console.error('Failed to fetch data');
      }
    } catch (error) {
      console.error('Error fetching data:', error);
    } finally {
      setRefreshing(false);
      setLoadingMore(false);
      setInitialLoader(false);
    }
  };

  useEffect(() => {
    fetchData(pageNo);
  }, []);

  // Pull-to-refresh
  const handleRefresh = useCallback(() => {
    setRefreshing(true);
    fetchData(1); // Refresh from the first page
  }, []);

  // Load more data
  const loadMore = () => {
    if (!loadingMore && pageNo < totalPages) {
      setLoadingMore(true);
      fetchData(pageNo + 1);
    }
  };

  return {
    data,
    totalResult,
    refreshing,
    loadingMore,
    handleRefresh,
    loadMore,
    initialLoader,
  };
};

export default usePagination;
Enter fullscreen mode Exit fullscreen mode

Step 3: Integrate the usePagination Hook into Your Component

With the usePagination hook ready, let’s integrate it into a React Native screen component, such as FeedsListScreen. This component will utilize FlatList for rendering the data with pagination and pull-to-refresh enabled.

FeedsListScreen Component:

import React from 'react';
import {
  FlatList,
  View,
  ActivityIndicator,
  RefreshControl,
} from 'react-native';
import usePagination from './usePagination';

const FeedsListScreen = () => {
  const {
    data,
    totalResult,
    refreshing,
    loadingMore,
    handleRefresh,
    loadMore,
    initialLoader,
  } = usePagination();

  const renderFooter = () => {
    if (!loadingMore || data.length < 8) return null; // Show footer loader only for subsequent pages
    return <ActivityIndicator animating size="large" />;
  };

  return initialLoader ? (
    <ActivityIndicator size="large" />
  ) : (
    <FlatList
      data={data}
      keyExtractor={(item) => item._id.toString()}
      renderItem={({ item }) => (
        <View>
          {/* Customize your item view here */}
        </View>
      )}
      refreshControl={
        <RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
      }
      ListFooterComponent={renderFooter}
      onEndReached={loadMore}
      onEndReachedThreshold={0.1}
    />
  );
};

export default FeedsListScreen;
Enter fullscreen mode Exit fullscreen mode

Explanation of the Key Parts

  1. Fetching Data with Pagination:

    • The fetchData function fetches data based on the page number, adds it to the existing dataset, and updates total results and total pages.
    • useEffect triggers the initial data fetch on mount.
  2. Pull-to-Refresh:

    • handleRefresh sets the page to the first page and calls fetchData, effectively refreshing the list.
  3. Loading More Data on Scroll:

    • loadMore triggers only if more pages are available. It increments the pageNo to fetch the next set of data, and updates the existing data with new entries.
  4. Customizing UI with renderFooter:

    • This displays an ActivityIndicator at the bottom only when additional data is being loaded for better user feedback.

Final Thoughts

This approach encapsulates pagination and pull-to-refresh into a reusable custom hook, making it modular and easy to integrate into various components. By using React Native’s FlatList and RefreshControl along with this custom hook, you can seamlessly handle large datasets while providing a responsive user experience.

With usePagination, managing paginated data and refreshing becomes straightforward, allowing for scalable, efficient data handling across your app.

Top comments (0)