React Native / Infinite Scroll

Infinite Scroll

Social media feed-ийг гүйлгэж байхад өгөгдөл дуусдаггүй — энэ нь infinite scroll буюу хязгааргүй гүйлгэлт юм. Хэрэглэгч жагсаалтын эцэст хүрэх үед апп автоматаар дараагийн хуудсыг серверээс татаж нэмдэг. FlatListonEndReached prop ашиглан энэ зан үйлийг хэрэгжүүлнэ.

onEndReached үндэс

FlatListonEndReached болон onEndReachedThreshold prop нэмнэ. threshold нь 0–1 хооронд утга авах бөгөөд жагсаалтын хэдэн хувьд хүрэхэд callback дуудагдахыг тодорхойлно. 0.5 гэдэг нь жагсаалтын доод 50%-д хүрэхэд дуудагдана гэсэн үг.

jsx
import { useState, useCallback } from "react";
import {
  View,
  Text,
  FlatList,
  ActivityIndicator,
  StyleSheet,
} from "react-native";

const PAGE_SIZE = 10;

export default function InfiniteList() {
  const [posts, setPosts] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);

  const fetchPage = useCallback(
    async (pageNum) => {
      if (loading || !hasMore) return;
      setLoading(true);
      try {
        const res = await fetch(
          `https://jsonplaceholder.typicode.com/posts?_page=${pageNum}&_limit=${PAGE_SIZE}`,
        );
        const data = await res.json();
        if (data.length < PAGE_SIZE) setHasMore(false);
        setPosts((prev) => [...prev, ...data]);
        setPage(pageNum + 1);
      } finally {
        setLoading(false);
      }
    },
    [loading, hasMore],
  );

  // Анхны ачаалалт
  useState(() => {
    fetchPage(1);
  }, []);

  const renderFooter = () => {
    if (!loading) return null;
    return (
      <View style={styles.footer}>
        <ActivityIndicator size="small" color="#22d3ee" />
      </View>
    );
  };

  return (
    <FlatList
      data={posts}
      keyExtractor={(item) => item.id.toString()}
      renderItem={({ item }) => (
        <View style={styles.card}>
          <Text style={styles.title}>{item.title}</Text>
          <Text style={styles.body} numberOfLines={2}>
            {item.body}
          </Text>
        </View>
      )}
      onEndReached={() => fetchPage(page)}
      onEndReachedThreshold={0.5}
      ListFooterComponent={renderFooter}
      contentContainerStyle={styles.list}
    />
  );
}

const styles = StyleSheet.create({
  list: {
    padding: 16,
    backgroundColor: "#0b1120",
  },
  card: {
    backgroundColor: "#0f172a",
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
    borderWidth: 1,
    borderColor: "#1e293b",
  },
  title: {
    color: "#f1f5f9",
    fontSize: 15,
    fontWeight: "600",
    marginBottom: 6,
    textTransform: "capitalize",
  },
  body: {
    color: "#94a3b8",
    fontSize: 13,
    lineHeight: 18,
  },
  footer: {
    paddingVertical: 20,
    alignItems: "center",
  },
});

hasMore flag маш чухал — сервер бүх өгөгдлийг илгээж дууссан бол дахин fetch хийхгүй байхад ашиглана.

useEffect-тэй анхны ачаалалт

Дээрх жишээнд анхны ачаалалтыг буруу аргаар дуудсан. Зөв хэлбэр нь useEffect ашиглах:

jsx
import { useState, useEffect, useCallback } from "react";
import {
  View,
  Text,
  FlatList,
  ActivityIndicator,
  StyleSheet,
} from "react-native";

export default function NewsFeed() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [loadingMore, setLoadingMore] = useState(false);
  const [hasMore, setHasMore] = useState(true);
  const [initialLoading, setInitialLoading] = useState(true);

  const load = useCallback(async (pageNum) => {
    if (pageNum === 1) setInitialLoading(true);
    else setLoadingMore(true);

    try {
      const res = await fetch(
        `https://jsonplaceholder.typicode.com/comments?_page=${pageNum}&_limit=15`,
      );
      const data = await res.json();
      if (data.length < 15) setHasMore(false);

      setItems((prev) => (pageNum === 1 ? data : [...prev, ...data]));
      setPage(pageNum + 1);
    } finally {
      setInitialLoading(false);
      setLoadingMore(false);
    }
  }, []);

  useEffect(() => {
    load(1);
  }, []);

  if (initialLoading) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" color="#22d3ee" />
        <Text style={styles.loadingText}>Ачааллаж байна...</Text>
      </View>
    );
  }

  return (
    <FlatList
      data={items}
      keyExtractor={(item) => item.id.toString()}
      renderItem={({ item }) => (
        <View style={styles.item}>
          <Text style={styles.name}>{item.name}</Text>
          <Text style={styles.email}>{item.email}</Text>
        </View>
      )}
      onEndReached={() => hasMore && !loadingMore && load(page)}
      onEndReachedThreshold={0.4}
      ListFooterComponent={() =>
        loadingMore ? (
          <View style={styles.footer}>
            <ActivityIndicator size="small" color="#22d3ee" />
          </View>
        ) : !hasMore ? (
          <Text style={styles.endText}>— Бүх мэдэгдэл уншигдсан —</Text>
        ) : null
      }
      contentContainerStyle={styles.list}
    />
  );
}

const styles = StyleSheet.create({
  center: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#0b1120",
    gap: 12,
  },
  loadingText: {
    color: "#94a3b8",
    fontSize: 14,
  },
  list: {
    padding: 16,
    backgroundColor: "#0b1120",
  },
  item: {
    backgroundColor: "#0f172a",
    padding: 14,
    borderRadius: 10,
    marginBottom: 10,
    borderWidth: 1,
    borderColor: "#1e293b",
  },
  name: {
    color: "#f1f5f9",
    fontSize: 14,
    fontWeight: "600",
    marginBottom: 4,
  },
  email: {
    color: "#22d3ee",
    fontSize: 12,
  },
  footer: {
    paddingVertical: 20,
    alignItems: "center",
  },
  endText: {
    color: "#475569",
    textAlign: "center",
    paddingVertical: 20,
    fontSize: 13,
  },
});

Infinite scroll + pull to refresh хослуулах

Жинхэнэ апп-д хоёуланг нь нэгэн зэрэг ашигладаг:

jsx
import { RefreshControl } from "react-native";

// FlatList дотор хоёуланг нэмнэ
<FlatList
  data={items}
  keyExtractor={(item) => item.id.toString()}
  renderItem={renderItem}
  // Infinite scroll
  onEndReached={() => hasMore && !loadingMore && load(page)}
  onEndReachedThreshold={0.5}
  ListFooterComponent={renderFooter}
  // Pull to refresh
  refreshControl={
    <RefreshControl
      refreshing={refreshing}
      onRefresh={async () => {
        setRefreshing(true);
        setHasMore(true);
        await load(1);
        setRefreshing(false);
      }}
      tintColor="#22d3ee"
      colors={["#22d3ee"]}
    />
  }
/>;

Infinite scroll бол орчин үеийн мобайл апп-н стандарт функц. Энэ хэрэгжүүлж чадвал таны апп жинхэнэ мэргэжлийн харагдах болно.

Дараагийн хичээлд:

Маягт ба баталгаажуулалт — TextInput ашиглан нэвтрэх маягт, бүртгэлийн маягт хэрхэн зөв хийх талаар сурна.