+3

Sử dụng Load More và Refresh với Flatlist React native

Trong bài viết này, mình sẽ chia sẻ hai tính năng là "Load More" và "Refresh" trong Flatlist. Load More cho phép người dùng tải thêm dữ liệu khi người dùng cuộn xuống cuối danh sách, trong khi tính năng Refresh giúp làm mới danh sách hiện tại.

Tại sao Load More và Refresh là quan trọng?

Khi xây dựng ứng dụng di động, trải nghiệm người dùng là yếu tố then chốt. Đối với các danh sách dài, việc tải một lượng lớn dữ liệu ngay từ đầu sẽ gây giảm hiệu suất. Để giải quyết vấn đề này, Load More cho phép người dùng tải từng phần dữ liệu.

Mặt khác, Refresh là tính năng quan trọng để cập nhật dữ liệu hiện tại khi người dùng muốn xem những thông tin mới nhất. Điều này tạo ra một trải nghiệm người dùng linh hoạt và luôn cập nhật.

Nói chung, mục đích của 2 tính năng này là giúp tăng trải nghiệm người dùng. Bài viết này mình chia sẻ dưới kinh nghiệm của bản thân, cũng xin góp ý từ các bạn để cải thiện.

Cùng bắt đầu nào )-_-(

Tạo ra các state:

  const limit = 10;
  const [data, setData] = useState([]);
  // Mục đich xử lý giao diện khi callApi
  const [UI, setUI] = useState(false);
  // Khi Loadmore mà hết dữ liệu =>>true
  const isStop = useRef<boolean>(false);
  // Hạn chế việc loading, luôn luôn 1 api được chạy
  const isLoading = useRef<boolean>(false);

Call Api (kết hợp Load more, Refresh):

const getData = async (type: "refresh" | "loadMore") => {
    if (isLoading.current == true) return; // Nếu đang loading mà vẫn gọi hàm ==> return
    if (type == "loadMore" && isStop.current == true) return; // Khi hết dữ liệu ==> return
    if (type == "refresh") {
      setData([]);
      isStop.current = false;
    }
    try {
      setUI(true);
      isLoading.current = true;
      //call api
      const response = await callApi({
        skip: type == "loadMore" ? data.length : 0,
        limit: limit,
      });
      //   await new Promise((resolve) => setTimeout(resolve, 1000));
      isLoading.current = false;
      if (response.products.length < limit) {
        isStop.current = true;
      }
      if (type == "refresh") {
        setData(response?.products);
      }
      if (type == "loadMore") {
        setData(data.concat(response?.products));
      }
    } catch (error) {
      console.log(error);
    } finally {
      setUI(false);
    }
  };

Mình có sử dụng 1 api trên dummyjson

const callApi = async (data: { skip: number; limit: number }) => {
  try {
    const response = await fetch(
      `https://dummyjson.com/products?skip=${data.skip}&limit=${data.limit}`
    );
    const result = await response.json();
    console.log("Result:", result);
    return result;
  } catch (error) {
    console.log(error);
  }
};

Flatlist:

  <View style={{ flex: 1 }}>
      <HeaderScreen title="Flatlist" />
      <FlatList
        data={data}
        keyExtractor={(item, idx) => idx + ""}
        renderItem={renderItem}
        onEndReachedThreshold={0.3}
        onEndReached={() => getData("loadMore")}
        refreshControl={
          <RefreshControl
            refreshing={false}
            onRefresh={() => getData("refresh")}
          />
        }
        ListFooterComponent={
          <View style={{ alignItems: "center", marginVertical: 10 }}>
            {renderFooterList}
          </View>
        }
      />
    </View>

Sử dụng listFooterComponent để thể hiện giao diện khi đang call api, danh sách sản phẩm trống, hoặc danh sách đã hết

  const renderFooterList = useMemo(() => {
    if (UI) return <ActivityIndicator color={"red"} />;
    if (data.length == 0 && isStop.current) return <Text>Danh sách trống</Text>;
    if (isStop.current) return <Text>Danh sách đã hết</Text>;
    return <View />;
  }, [UI]);

Dưới đây là full code, các bạn cùng trải nghiệm nhé

import HeaderScreen from "components/headers/HeaderScreen";
import React, { useEffect, useMemo, useRef, useState } from "react";
import {
  View,
  FlatList,
  RefreshControl,
  ActivityIndicator,
  Text,
} from "react-native";

const callApi = async (data: { skip: number; limit: number }) => {
  try {
    const response = await fetch(
      `https://dummyjson.com/products?skip=${data.skip}&limit=${data.limit}`
    );
    const result = await response.json();
    console.log("Result:", result);
    return result;
  } catch (error) {
    console.log(error);
  }
};

const Flatlist = () => {
  const limit = 10;
  const [data, setData] = useState([]);
  // Mục đich xử lý giao diện khi callApi
  const [UI, setUI] = useState(false);
  // Khi Loadmore mà hết dữ liệu =>>true
  const isStop = useRef<boolean>(false);
  // Hạn chế việc loading, luôn luôn 1 api được chạy
  const isLoading = useRef<boolean>(false);
  useEffect(() => {
    getData("refresh");
  }, []);

  const getData = async (type: "refresh" | "loadMore") => {
    if (isLoading.current == true) return; // Nếu đang loading mà vẫn gọi hàm ==> return
    if (type == "loadMore" && isStop.current == true) return; // Khi hết dữ liệu ==> return
    if (type == "refresh") {
      setData([]);
      isStop.current = false;
    }
    try {
      setUI(true);
      isLoading.current = true;
      //call api
      const response = await callApi({
        skip: type == "loadMore" ? data.length : 0,
        limit: limit,
      });
      await new Promise((resolve) => setTimeout(resolve, 1000));
      isLoading.current = false;
      if (response.products.length < limit) {
        isStop.current = true;
      }
      if (type == "refresh") {
        setData(response?.products);
      }
      if (type == "loadMore") {
        setData(data.concat(response?.products));
      }
    } catch (error) {
      console.log(error);
    } finally {
      setUI(false);
    }
  };

  const renderFooterList = useMemo(() => {
    if (UI) return <ActivityIndicator color={"red"} />;
    if (data.length == 0 && isStop.current) return <Text>Danh sách trống</Text>;
    if (isStop.current) return <Text>Danh sách đã hết</Text>;
    return <View />;
  }, [UI]);

  const renderItem = ({ item }: any) => {
    return (
      <Text
        style={{
          padding: 40,
          backgroundColor: item.id % 2 ? "#ffffff" : "#bbbbbb",
        }}
      >
        {item?.title}
      </Text>
    );
  };
  return (
    <View style={{ flex: 1 }}>
      <HeaderScreen title="Flatlist" />
      <FlatList
        data={data}
        keyExtractor={(item, idx) => idx + ""}
        renderItem={renderItem}
        onEndReachedThreshold={0.3}
        onEndReached={() => getData("loadMore")}
        refreshControl={
          <RefreshControl
            refreshing={false}
            onRefresh={() => getData("refresh")}
          />
        }
        ListFooterComponent={
          <View style={{ alignItems: "center", marginVertical: 10 }}>
            {renderFooterList}
          </View>
        }
      />
    </View>
  );
};

export default Flatlist;


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.