في التصفّح العادي بتجيب صفحة وترمي اللي قبلها. useInfiniteQuery يجمع الصفحات في مصفوفة مستمرة. «اعرض المزيد» يضيف، ومش بينسى.
ده اللي بيحصل وأنت بتنزل في خط زمني: القديم فضل، والجديد تلزق تحته.
السيرفر يقول فيه تالي ولا لأ
app.get("/api/students", async (req, res) => { const page = parseInt(req.query.page) || 1; const limit = 20; const students = await prisma.student.findMany({ skip: (page - 1) * limit, take: limit, }); const total = await prisma.student.count(); const hasMore = page * limit < total; res.json({ students, nextPage: hasMore ? page + 1 : null, });});مهم إن الرد فيه إشارة للجاي. null هنا معناها مفيش صفحة بعد كده. راجع أسماء دوال Prisma في الدوكس، لأنها بتتغير.
الواجهة
import { useInfiniteQuery } from "@tanstack/react-query";function getStudents({ pageParam }) { return fetch(`/api/students?page=${pageParam}`).then((res) => res.json());}function StudentsList() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isPending, } = useInfiniteQuery({ queryKey: ["students"], queryFn: getStudents, initialPageParam: 1, getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined, }); if (isPending) return <p>جاري التحميل...</p>; return ( <div> {data.pages.map((page, i) => ( <div key={i}> {page.students.map((s) => ( <div key={s.id}>{s.name}</div> ))} </div> ))} <button onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage} > {isFetchingNextPage ? "بيحمّل..." : hasNextPage ? "عرض المزيد" : "مفيش أكتر"} </button> </div> );}getNextPageParam بتتنفذ بعد كل صفحة. آخر صفحة هي اللي السيرفر رجّعها دلوقتي. المعامل التاني، لو احتجته، كل الصفحات لحد دلوقتي. لو رجّعت undefined، المكتبة تقفل hasNextPage.
fetchNextPage تاخد القيمة دي وتبعتها لوحدها.
data مش مصفوفة طلاب. فيها pages للردود، و pageParams للأرقام اللي اتبعتت. عشان كده فيه لف على الصفحات، وبعدين لف على طلاب كل صفحة.
isFetchingNextPage وقت الصفحة الزيادة بس. isPending لأول تحميل، قبل أي صفحة.
السكرول ينادي لوحده
حط عنصر في آخر القائمة. لما يظهر في الشاشة، نادِ الصفحة الجاية.
import { useEffect, useRef } from "react";function StudentsList() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isPending, } = useInfiniteQuery({ queryKey: ["students"], queryFn: getStudents, initialPageParam: 1, getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined, }); const observerRef = useRef(null); useEffect(() => { if (!observerRef.current || !hasNextPage) return; const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && !isFetchingNextPage) { fetchNextPage(); } }, { threshold: 1 }, ); observer.observe(observerRef.current); return () => observer.disconnect(); }, [hasNextPage, isFetchingNextPage, fetchNextPage]); if (isPending) return <p>جاري التحميل...</p>; return ( <div> {data.pages.map((page, i) => ( <div key={i}> {page.students.map((s) => ( <div key={s.id}>{s.name}</div> ))} </div> ))} <div ref={observerRef} style={{ height: 1 }} /> {isFetchingNextPage && <p>بيحمّل المزيد...</p>} </div> );}لورا كمان
الشات اللي بيطلّع رسائل أقدم محتاج اتجاهين. getPreviousPageParam على أول صفحة، وبعدين fetchPreviousPage و hasPreviousPage.
useInfiniteQuery({ queryKey: ["messages"], queryFn: getMessages, initialPageParam: 0, getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined, getPreviousPageParam: (firstPage) => firstPage.prevCursor ?? undefined,});رقم الصفحة يتلخبط
لو حد ضاف طالب وأنت في نص السكرول، تخطي الصفحات بالأرقام يزوّد أو يكرّر صفوف. المؤشر أثبت: كمّل بعد آخر id شفته.
app.get("/api/students", async (req, res) => { const cursor = req.query.cursor; const students = await prisma.student.findMany({ take: 20, skip: cursor ? 1 : 0, cursor: cursor ? { id: cursor } : undefined, orderBy: { id: "asc" }, }); res.json({ students, nextCursor: students.length === 20 ? students.at(-1).id : null, });});useInfiniteQuery({ queryKey: ["students"], queryFn: ({ pageParam }) => fetch(`/api/students?cursor=${pageParam ?? ""}`).then((r) => r.json()), initialPageParam: null, getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,});لقوائم الطلاب والدرجات الكبيرة، المؤشر أهدى من رقم الصفحة. skip: 1 عشان المؤشر نفسه ما يترجعش تاني مع الصفحة الجاية. راجع شكل المؤشر في دوكس Prisma قبل ما تثبّته.