Jak zaimplementować nieskończone przewijanie i paginację za pomocą Next.js i zapytania TanStack

Większość aplikacji, które stworzysz, będzie zarządzać danymi; w miarę zwiększania się skali programów może być ich coraz więcej. Jeśli aplikacje nie zarządzają skutecznie dużymi ilościami danych, działają słabo.

Paginacja i nieskończone przewijanie to dwie popularne techniki, których można użyć do optymalizacji wydajności aplikacji. Mogą pomóc w wydajniejszym renderowaniu danych i poprawić ogólne wrażenia użytkownika.

Paginacja i nieskończone przewijanie za pomocą zapytania TanStack

Zapytanie TanStack—adaptacja React Query — to solidna biblioteka do zarządzania stanem dla aplikacji JavaScript. Oferuje wydajne rozwiązanie do zarządzania stanem aplikacji, a także innymi funkcjonalnościami, w tym zadaniami związanymi z danymi, takimi jak buforowanie.

Paginacja polega na podzieleniu dużego zbioru danych na mniejsze strony, umożliwiając użytkownikom nawigację po treści w łatwych do zarządzania fragmentach za pomocą przycisków nawigacyjnych. Natomiast nieskończone przewijanie zapewnia bardziej dynamiczne przeglądanie. Gdy użytkownik przewija, nowe dane ładują się i wyświetlają automatycznie, eliminując potrzebę jawnej nawigacji.

Paginacja i nieskończone przewijanie mają na celu efektywne zarządzanie i prezentację dużych ilości danych. Wybór pomiędzy nimi zależy od wymagań aplikacji dotyczących danych.

Kod tego projektu znajdziesz w this GitHub magazyn.

Konfigurowanie projektu Next.js

Aby rozpocząć, utwórz projekt Next.js. Zainstaluj najnowszą wersję Next.js 13, która korzysta z katalogu aplikacji.

 npx create-next-app@latest next-project --app 

Następnie zainstaluj pakiet TanStack w swoim projekcie za pomocą npm, menedżera pakietów Node.

 npm i @tanstack/react-query 

Zintegruj zapytanie TanStack z aplikacją Next.js

Aby zintegrować TanStack Query z projektem Next.js, musisz utworzyć i zainicjować nową instancję TanStack Query w katalogu głównym aplikacji – plik układ.js. Aby to zrobić, zaimportuj QueryClient i QueryClientProvider z TanStack Query. Następnie zawiń element dziecięcy za pomocą QueryClientProvider w następujący sposób:

 "use client"
import React from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({ children }) {
  const queryClient = new QueryClient();

  return (
    <html lang="en">
      <body>
        <QueryClientProvider client={queryClient}>
          {children}
        </QueryClientProvider>
      </body>
    </html>
  );
}

export { metadata };

Ta konfiguracja zapewnia, że ​​TanStack Query ma pełny dostęp do stanu aplikacji.

Hak useQuery usprawnia pobieranie danych i zarządzanie nimi. Podając parametry paginacji, takie jak numery stron, można łatwo pobrać określone podzbiory danych.

Dodatkowo hak zapewnia różne opcje i konfiguracje umożliwiające dostosowanie funkcjonalności pobierania danych, w tym ustawianie opcji pamięci podręcznej, a także efektywną obsługę stanów ładowania. Dzięki tym funkcjom możesz z łatwością stworzyć płynną paginację.

Teraz, aby zaimplementować paginację w aplikacji Next.js, utwórz plik Pagination/page.js w katalogu src/app. Wewnątrz tego pliku dokonaj następującego importu:

 "use client"
import React, { useState } from 'react';
import { useQuery} from '@tanstack/react-query';
import './page.styles.css';

Następnie zdefiniuj komponent funkcjonalny React. Wewnątrz tego komponentu musisz zdefiniować funkcję, która będzie pobierać dane z zewnętrznego API. W takim wypadku skorzystaj z API zastępcze JSON aby pobrać zestaw postów.

 export default function Pagination() {
  const [page, setPage] = useState(1);

  const fetchPosts = async () => {
    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/posts?
                                  _page=${page}&_limit=10`);

      if (!response.ok) {
        throw new Error('Failed to fetch posts');
      }

      const data = await response.json();
      return data;
    } catch (error) {
      console.error(error);
      throw error;
    }
  };

  
}

Teraz zdefiniuj hak useQuery i określ następujące parametry jako obiekty:

   const { isLoading, isError, error, data } = useQuery({
    keepPreviousData: true,
    queryKey: ['posts', page],
    queryFn: fetchPosts,
  });

Wartość keepPreviousData ma wartość true, co gwarantuje, że podczas pobierania nowych danych aplikacja zachowa poprzednie dane. Parametr queryKey to tablica zawierająca klucz zapytania, w tym przypadku punkt końcowy i bieżącą stronę, dla której chcesz pobrać dane. Na koniec parametr queryFn fetchPosts wyzwala wywołanie funkcji w celu pobrania danych.

Jak wspomniano wcześniej, hak udostępnia kilka stanów, które można rozpakować, podobnie jak niszczy się tablice i obiekty i wykorzystuje je do poprawy komfortu użytkownika (renderowania odpowiednich interfejsów użytkownika) podczas procesu pobierania danych. Stany te obejmują isLoading, isError i inne.

Aby to zrobić, dołącz następujący kod, aby wyświetlić różne ekrany komunikatów w zależności od bieżącego stanu trwającego procesu:

   if (isLoading) {
    return (<h2>Loading...</h2>);
  }

  if (isError) {
    return (<h2 className="error-message">{error.message}</h2>);
  }

Na koniec dołącz kod elementów JSX, które będą renderowane na stronie przeglądarki. Kod ten pełni także dwie inne funkcje:

  • Gdy aplikacja pobierze posty z API, zostaną one zapisane w zmiennej danych dostarczonej przez hak useQuery. Ta zmienna pomaga zarządzać stanem aplikacji. Następnie możesz zmapować listę postów przechowywanych w tej zmiennej i wyrenderować je w przeglądarce.
  • Aby dodać dwa przyciski nawigacyjne, Poprzedni i Następny, aby umożliwić użytkownikom wysyłanie zapytań i wyświetlanie dodatkowych danych podzielonych na strony.
   return (
    <div>
      <h2 className="header">Next.js Pagination</h2>
      {data && (
        <div className="card">
          <ul className="post-list">
            {data.map((post) => (
                <li key={post.id} className="post-item">{post.title}</li>
            ))}
          </ul>
        </div>
      )}
      <div className="btn-container">
        <button
          onClick={() => setPage(prevState => Math.max(prevState - 1, 0))}
          disabled={page === 1}
          className="prev-button"
        >Prev Page</button>

        <button
          onClick={() => setPage(prevState => prevState + 1)}
          className="next-button"
        >Next Page</button>
      </div>
    </div>
  );

Na koniec uruchom serwer deweloperski.

 npm run dev 

Następnie przejdź do http://localhost:3000/Pagination w przeglądarce.

Ponieważ folder Pagination znalazł się w katalogu aplikacji, Next.js traktuje go jako trasę, umożliwiając dostęp do strony pod tym adresem URL.

Nieskończone przewijanie zapewnia płynne przeglądanie. Dobrym przykładem jest YouTube, który automatycznie pobiera nowe filmy i wyświetla je podczas przewijania w dół.

Hak useInfiniteQuery umożliwia zaimplementowanie nieskończonego przewijania poprzez pobieranie danych z serwera na stronach oraz automatyczne pobieranie i renderowanie następnej strony danych, gdy użytkownik przewija w dół.

Aby zaimplementować nieskończone przewijanie, dodaj plik InfiniteScroll/page.js w katalogu src/app. Następnie wykonaj następujący import:

 "use client"
import React, { useRef, useEffect, useState } from 'react';
import { useInfiniteQuery } from '@tanstack/react-query';
import './page.styles.css';

Następnie utwórz komponent funkcjonalny React. Wewnątrz tego komponentu, podobnie jak w przypadku implementacji paginacji, utwórz funkcję, która będzie pobierać dane z postów.

 export default function InfiniteScroll() {
  const listRef = useRef(null);
  const [isLoadingMore, setIsLoadingMore] = useState(false);

  const fetchPosts = async ({ pageParam = 1 }) => {
    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/posts?
                                  _page=${pageParam}&_limit=5`);

      if (!response.ok) {
        throw new Error('Failed to fetch posts');
      }

      const data = await response.json();
      await new Promise((resolve) => setTimeout(resolve, 2000));
      return data;
    } catch (error) {
      console.error(error);
      throw error;
    }
  };

  
}

W przeciwieństwie do implementacji paginacji, ten kod wprowadza dwusekundowe opóźnienie podczas pobierania danych, aby umożliwić użytkownikowi eksplorację bieżących danych podczas przewijania i wyzwolenie ponownego pobrania nowego zestawu danych.

Teraz zdefiniuj hak useInfiniteQuery. Gdy komponent zostanie początkowo zamontowany, hak pobierze pierwszą stronę danych z serwera. Gdy użytkownik przewija w dół, hak automatycznie pobierze następną stronę danych i wyrenderuje ją w komponencie.

   const { data, fetchNextPage, hasNextPage, isFetching } = useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    getNextPageParam: (lastPage, allPages) => {
      if (lastPage.length < 5) {
        return undefined;
      }
      return allPages.length + 1;
    },
  });

  const posts = data ? data.pages.flatMap((page) => page) : [];

Zmienna posty łączy wszystkie posty z różnych stron w jedną tablicę, co daje spłaszczoną wersję zmiennej danych. Umożliwia to łatwe mapowanie i renderowanie poszczególnych postów.

Aby śledzić przewijanie użytkownika i ładować więcej danych, gdy użytkownik znajduje się blisko dołu listy, można zdefiniować funkcję wykorzystującą interfejs API Intersection Observer do wykrywania, kiedy elementy przecinają się z rzutnią.

   const handleIntersection = (entries) => {
    if (entries[0].isIntersecting && hasNextPage && !isFetching && !isLoadingMore) {
      setIsLoadingMore(true);
      fetchNextPage();
    }
  };

  useEffect(() => {
    const observer = new IntersectionObserver(handleIntersection, { threshold: 0.1 });

    if (listRef.current) {
      observer.observe(listRef.current);
    }

    return () => {
      if (listRef.current) {
        observer.unobserve(listRef.current);
      }
    };
  }, [listRef, handleIntersection]);

  useEffect(() => {
    if (!isFetching) {
      setIsLoadingMore(false);
    }
  }, [isFetching]);

Na koniec dołącz elementy JSX do postów renderowanych w przeglądarce.

   return (
    <div>
      <h2 className="header">Infinite Scroll</h2>
      <ul ref={listRef} className="post-list">
        {posts.map((post) => (
          <li key={post.id} className="post-item">
            {post.title}
          </li>
        ))}
      </ul>
      <div className="loading-indicator">
        {isFetching ? 'Fetching...' : isLoadingMore ? 'Loading more...' : null}
      </div>
    </div>
  );

Po wprowadzeniu wszystkich zmian odwiedź http://localhost:3000/InfiniteScroll, aby zobaczyć je w akcji.

Zapytanie TanStack: więcej niż tylko pobieranie danych

Paginacja i nieskończone przewijanie to dobre przykłady podkreślające możliwości TanStack Query. Mówiąc najprościej, jest to wszechstronna biblioteka do zarządzania danymi.

Dzięki rozbudowanemu zestawowi funkcji możesz usprawnić procesy zarządzania danymi aplikacji, w tym wydajną obsługę stanu. Oprócz innych zadań związanych z danymi możesz poprawić ogólną wydajność aplikacji internetowych, a także komfort użytkowania.