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