آموزش پیشرفته ریاکت کوئری | pagination | infinite scroll

دوستان عزیز سلام. امیدوارم حالتون خوب باشه. در بخش اول مقاله آموزشی کتابخانه react-query به معرفی این کتابخانه کاربردی پرداختیم و نحوه دریافت داده از سرور و همچنین ارسال داده به سرور را توسط هوک های useQuery و useMutation آموزش دادیم.
در این مقاله قصد داریم مباحث پیچیده تر لایبرری react query را بررسی کنیم. مواردی مانند cacheTime و staleTime و صفحه بندی و اسکرول بی نهایت و…
مفاهیم staleTime و cacheTime:
staleTime در تنظیمات هوک useQuery بیانگر مقدار زمان لازم برای کهنه شدن دیتای دریافتی از سرور است. پیش فرض آن صفر ثانیه است.
مثلا اگر staleTime: 2000 باشد، پس از فوکوس روی تب مرورگر تا 2 ثانیه دیتاها تازه هستند و بعد از آن stale می شوند و در صورت لزوم refetch رخ می دهد.
دیتای کش شده بعد از cacheTime – پیش فرض 5 دقیقه – منقضی می شود. زمان کش را از هنگام اکتیو بودن useQuery محاسبه می کنند. پس از انقضای دیتای کش شده، garbage collector به میدان می آید!
در هنگام فچ شدن دیتا از سرور می توان از دیتای کش شده برای نمایش به کاربر بجای نمایش صفحه سفید خالی (blank page) استفاده کرد. اما اگر نمی خواهید هیچ دیتای کش شده ای نمایش دهید باید کانفیگ cacheTime را برابر صفر قرار دهید.
پکیج ری اکت کوئری DevTools:
یکی از ابزارهای مفید هنگام کار با لایبرری react-query پکیج react-query-devtools است. برای نصب آن یکی از دستورات زیر را اجرا کنید:
$ npm i @tanstack/react-query-devtools # or $ pnpm add @tanstack/react-query-devtools # or $ yarn add @tanstack/react-query-devtools
سپس لازم است آن را به فایل app.js پروژه خود اضافه کنید. به شکل زیر:
import { ReactQueryDevtools } from '@tanstack/react-query-devtools' function App() { return ( <QueryClientProvider client={queryClient}> {/* The rest of your application */} <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> ) }
در اینحالت اگر در پروژه خود از react-query استفاده کنید یک آیکون در سمت چپ و پایین صفحه مرورگرتان ظاهر می شود و با کلیک روی آن می توانید از امکانات این ابزار مفید استفاده کنید.
برخی از امکاناتی که ابزار ری اکت کوئری devtools در اختیار برنامه نویسان قرار می دهد عبارتند از:
نمایش staleTime, cacheTime, fetching, stale data, …
آرایه وابستگی در useQuery:
فرض کنید یک پروژه بلاگ داریم و می خواهیم کامنت های مربوط به هر پست را به کاربران نمایش دهیم. اگر در آرایه وابستگی هوک useQuery فقط کلید را تعریف کنیم با تغییر هر پست کامنت های آن آپدیت نمی شود و ثابت می ماند. برای اینکه پس از کلیک روی لینک هر پست کامنت های مربوط به آن نمایش یابد بایستی مانند زیر عمل کنیم.
به این منظور باید در آرایه وابستگی هوک useQuery علاوه بر نام کلید، آیدی پست را نیز تعریف کنیم. بدین شکل:
useQuery(["comments", post.id], fetchFunc)
در این حالت با تغییر آیدی پست، ریکت کوئری یک useQuery مجزا می سازد و اجرا می کند. مانند هوک useEffect باعث trigger شدن می شود.
در react-dev-tools وقتی روی پست 1 کلیک می کنیم و میخواهیم کامنت های آن را ببینیم، و سپس روی پست مثلا 4 کلیک می کنیم، آرایه [“comments”, 1] غیرفعال می شود. به رنگ خاکستری، به این معنیه که در کش وجود دارد و تا زمان انقضای cacheTime در آنجا باقی می ماند.
پیاده سازی صفحه بندی یا pagination:
برای pagination می توان هوک useQuery را به شکل زیر تعریف کرد:
useQuery(["posts", currentPage],() => fetchPosts(currentPage))
و یک استیت تعریف کرد برای currentPage و در دستور fetch مقدار currentPage را برای api call لحاظ کرد.
وقتی در صفحه بندی روی دکمه بعدی کلیک می کنیم، دیتا خالی می شود و تا زمانیکه دیتای صفحه بعدی فچ نشود صفحه خالی می شود. برای رفع این مشکل باید دیتاها را در کش ذخیره کرد. برای اینکار باید از prefetching استفاده کرد. در این حالت، دیتای دریافتی بطور اتوماتیک stale خواهد بود.
قابلیت prefetching فقط در pagination کاربرد ندارد. بلکه در مواردی که نیاز است یک دیتا از قبل از سرور دریافت شده باشد می تواند مفید باشد.
برای عملیات prefetching باید از هوک useEffect بهره ببریم. بصورت زیر:
useEffect(() => { // prefetch next page data from server: const nextPage = currentPage + 1; queryClient.prefetchQuery(["posts", nextPage], () => fetchPosts(nextPage)); }, [currentPage]);
باید هوک useQueryClient را ایمپورت کنیم:
import { useQuery, useQueryClient } from "react-query";
و مقداردهی اولیه:
const queryClient = useQueryClient();
در صورت prefetching، دیتای صفحه بعدی را از سرور دریافت می کنیم و با زدن دکمه بعد، هیچ پرشی در صفحه نخواهیم دید.
و برای اینکه وقتی روی دکمه “صفحه قبل” هم کلیک می کنیم دیتا در کش وجود داشته باشد باید کانفیگ زیر را به هوک useQuery بعنوان آرگومان سوم اضافه کنیم:
{ keepPreviousData: true, }
در تصویر زیر ابزار react dev tool را مشاهده می کنید:
با اینکه در صفحه 3 هستیم، صفحه 4 نیز از قبل fetch شده (prefetch) و در کش قرار گرفته.
فرق isFetching با isLoading:
isLoading زیرمجموعه ای از isFetching است. وقتی async query func هنوز resolve نشده باشد isFetching برابر true است. ولی isLoading علاوه بر این شرط، باید cached data هم خالی باشد تا true شود.
تفاوت useMutation و useQuery:
هوک useMutation یک فانکشن mutate برمیگرداند. این هوک به query key نیاز ندارد. مقدار isLoading دارد ولی isFetching ندارد زیرا در mutate دیتا کش نمی شود. بطور پیش فرض هیچ تلاش مجددی پس از fail شدن درخواست صورت نمی گیرد (retry) اما قابل تنظیم است.
استفاده از useMutation برای حذف پست:
ابتدا باید تابع fetch را تعریف کنیم:
const test = () => { const deletePost = async () => { return await fetch( `https://jsonplaceholder.typicode.com/postId/${post.id}`, { method: "DELETE" } ).then((res) => res.json()); }; };
سپس هوک useMutation را بصورت زیر ایجاد کنیم:
const deleteMutation = useMutation(() => deletePost(post.id));
و دکمه حذف را به شکل زیر:
<button onClick={() => deleteMutation.mutate(post.id)}>Delete</button> {deleteMutation.isError && <p style={{ color: "red" }}>Error Delete...!</p>} {deleteMutation.isLoading && ( <p style={{ color: "blue" }}>Deleting in progress...!</p> )} {deleteMutation.isSuccess && ( <p style={{ color: "green" }}>Success Delete...!</p> )}
در رویداد onClick دکمه باید تابع mutate را از آبجکت بازگشتی deleteMutation صدا بزنیم.
استفاده از useMutation برای آپدیت پست:
تعریف تابع آپدیت – متد patch:
const updatePost = async () => { return await fetch(`https://jsonplaceholder.typicode.com/postId/${post.id}`, { method: "PATCH", data: { title: "NEW TITLE HERE...!" }, }).then((res) => res.json()); };
تعریف updateMutation:
const updateMutation = useMutation(() => updatePost(post.id));
و در نهایت تعریف دکمه آپدیت:
<button onClick={() => updateMutation.mutate(post.id)}>Update</button>;
پیاده سازی اسکرول بی نهایت:
توسط هوک useInfiniteQuery
ابتدا باید لایبرری react-infinite-scroller را توسط دستور زیر نصب کرد:
npm i react-infinite-scroller
سپس در کامپوننت موردنظر موارد زیر را ایمپورت کرد:
import InfiniteScroll from "react-infinite-scroller"; import { useInfiniteQuery } from "react-query";
تابع fetch و متغیر url را به شکل زیر تعریف کنید:
const initialUrl = "https://swapi.dev/api/people/"; const fetchUrl = async (url) => { return await fetch(url).then((res) => res.json()); };
و کوئری فانکشن را به شکل زیر:
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery( "sw-people", ({ pageParam }) => fetchUrl(pageParam), { getNextPageParam: (lastPage) => lastPage.next || undefined } );
در تابع فوق، هوک useInfiniteQuery همانند useQuery آرگومان اولش برابر کلید یکتای کوئری است و آرگومان دومش تابع fetch است. آیتم های data, fetchNextPage و hasNextPage را بر می گرداند که می توان از آنها در ادامه استفاده کرد.
یک نگاهی به دیتای بازگشتی از api بندازیم:
برای ادامه کار نیاز به یک پکیج برای پیاده سازی اسکرول بینهایت در ری اکت داریم که ما از react-infinite-scroller استفاده می کنیم.
کامپوننت InfiniteScroll دو تا props می گیرد: hasMore که برابر با آیتم hasNextPage از هوک useInfiniteQuery است و loadMore که برابر fetchNextPage است. یعنی:
<InfiniteScroll hasMore={hasNextPage} loadMore={fetchNextPage} />
باید ابتدا وضعیت loading یا error را به شکل زیر بررسی کنید:
if (isLoading) return <p>Loading...</p>; if (isError) return <p>error... ({error.toString()})</p>;
سپس کامپوننت InfiniteScroll را بصورت زیر تکمیل کنید:
{ isFetching && ( <h3 style={{ position: "fixed", right: 0, color: "yellow" }}> {" "} Loading...!{" "} </h3> ); } <InfiniteScroll hasMore={hasNextPage} loadMore={fetchNextPage}> {" "} {data.pages.map((pageData) => { return pageData.results.map((person, index) => ( <Person key={index} name={person.name} hairColor={person.hair_color} eyeColor={person.eye_color} /> )); })}{" "} </InfiniteScroll>;
و کامپوننت Person:
import React from "react"; const Person = ({ name, hairColor, eyeColor }) => { return ( <li> {" "} {name}{" "} <ul> {" "} <li>Hair: {hairColor}</li> <li>Eye: {eyeColor}</li>{" "} </ul>{" "} </li> ); }; export default Person;
دوستان عزیز این مقاله بزودی بروز رسانی خواهد شد. پس همراه ما باشید و مجددا از آن بازدید نمایید.
دیدگاهتان را بنویسید