Większość tworzonych aplikacji w pewnym stopniu przetwarza dane. Wraz z rozbudową programów, ilość tych danych może dynamicznie rosnąć. Jeśli aplikacje nie radzą sobie efektywnie z dużymi zbiorami informacji, ich wydajność może drastycznie spaść.
Paginacja i przewijanie nieskończone to dwie popularne strategie optymalizacji działania aplikacji. Umożliwiają one sprawniejsze renderowanie danych, co przekłada się na lepsze wrażenia użytkownika.
Paginacja i nieskończone przewijanie z wykorzystaniem TanStack Query
TanStack Query, będący adaptacją React Query, to potężna biblioteka do zarządzania stanem w aplikacjach JavaScript. Oferuje efektywne rozwiązania w zakresie zarządzania stanem, jak również szereg innych funkcji, w tym tych związanych z danymi, takich jak buforowanie.

Paginacja polega na dzieleniu dużego zbioru danych na mniejsze, łatwiejsze do opanowania strony, po których użytkownik może nawigować za pomocą przycisków. Z kolei przewijanie nieskończone oferuje bardziej dynamiczne przeglądanie – gdy użytkownik przesuwa stronę w dół, nowe dane są automatycznie ładowane, bez konieczności używania przycisków nawigacyjnych.
Zarówno paginacja, jak i przewijanie nieskończone, służą efektywnemu zarządzaniu i prezentowaniu dużych ilości danych. Wybór między nimi zależy od specyficznych wymagań aplikacji.
Kod tego projektu znajduje się w tym repozytorium GitHub.
Konfiguracja projektu Next.js
Na początek, utwórz nowy 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, za pomocą menedżera pakietów Node, npm, zainstaluj bibliotekę TanStack w swoim projekcie.
npm i @tanstack/react-query
Integracja TanStack Query z aplikacją Next.js
Aby zintegrować TanStack Query z projektem Next.js, należy utworzyć i zainicjować nową instancję TanStack Query w głównym katalogu aplikacji – w pliku layout.js. W tym celu, zaimportuj QueryClient i QueryClientProvider z TanStack Query. Następnie, umieść element podrzędny (dziecko) wewnątrz QueryClientProvider, tak jak pokazano poniżej:
"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 dostęp do stanu całej aplikacji.
Hook `useQuery` ułatwia pobieranie i zarządzanie danymi. Podając parametry paginacji, takie jak numery stron, można z łatwością pobrać konkretne fragmenty danych.
Ponadto, hook oferuje różnorodne opcje konfiguracyjne, pozwalające dostosować funkcjonalność pobierania danych, w tym ustawianie opcji pamięci podręcznej, a także efektywne zarządzanie stanami ładowania. Dzięki tym możliwościom, można bez problemu zaimplementować sprawną paginację.
Aby wdrożyć paginację w aplikacji Next.js, utwórz plik `Pagination/page.js` w katalogu `src/app`. Wewnątrz tego pliku, dokonaj następujących importów:
"use client"
import React, { useState } from 'react';
import { useQuery} from '@tanstack/react-query';
import './page.styles.css';
Następnie zdefiniuj komponent funkcyjny React. W tym komponencie zdefiniuj funkcję, która pobiera dane z zewnętrznego API. W tym przypadku skorzystaj z JSON Placeholder API, 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 hook `useQuery` i przekaz parametry jako obiekty:
const { isLoading, isError, error, data } = useQuery({
keepPreviousData: true,
queryKey: ['posts', page],
queryFn: fetchPosts,
});
Ustawienie `keepPreviousData` na `true` gwarantuje, że aplikacja zachowa poprzednie dane podczas pobierania nowych. Parametr `queryKey` to tablica zawierająca klucz zapytania, w tym przypadku punkt końcowy oraz bieżącą stronę, dla której chcemy pobrać dane. Parametr `queryFn` `fetchPosts` uruchamia funkcję pobierającą dane.
Jak wspomniano, hook udostępnia kilka stanów, które można rozpakować, wykorzystując destrukturyzację tablic i obiektów. Te stany mogą być użyte do polepszenia doświadczenia użytkownika poprzez wyświetlanie odpowiednich interfejsów podczas pobierania danych. Stany te to między innymi: `isLoading`, `isError` i inne.
Aby zaimplementować ekrany komunikatów w zależności od bieżącego stanu procesu, dodaj następujący kod:
if (isLoading) {
return (<h2>Loading...</h2>);
}
if (isError) {
return (<h2 className="error-message">{error.message}</h2>);
}
Na koniec dodaj kod elementów JSX, które zostaną wyrenderowane na stronie. Kod ten pełni dwie funkcje:
- Gdy aplikacja pobierze posty z API, zapisuje je w zmiennej `data` dostarczonej przez hook `useQuery`. Ta zmienna ułatwia zarządzanie stanem aplikacji. Następnie można zmapować listę postów i wyrenderować je w przeglądarce.
- Dodaje dwa przyciski nawigacyjne, „Poprzednia strona” i „Następna strona”, które umożliwiają użytkownikom przełączanie między stronami z danymi.
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 zakończenie uruchom serwer developerski.
npm run dev
Następnie przejdź do adresu http://localhost:3000/Pagination w przeglądarce.

Ponieważ folder `Pagination` znajduje się w katalogu `app`, Next.js traktuje go jako trasę, umożliwiając dostęp do strony pod tym adresem URL.
Przewijanie nieskończone zapewnia płynne przeglądanie. Dobrym przykładem jest YouTube, który automatycznie pobiera nowe filmy podczas przewijania w dół.
Hook `useInfiniteQuery` umożliwia implementację przewijania nieskończonego poprzez pobieranie danych z serwera w podziałach na strony oraz automatyczne pobieranie i renderowanie kolejnych stron danych, gdy użytkownik przesuwa stronę w dół.
Aby wdrożyć nieskończone przewijanie, dodaj plik `InfiniteScroll/page.js` w katalogu `src/app`. Następnie dodaj następujące importy:
"use client"
import React, { useRef, useEffect, useState } from 'react';
import { useInfiniteQuery } from '@tanstack/react-query';
import './page.styles.css';
Następnie stwórz komponent funkcyjny React. Wewnątrz tego komponentu, podobnie jak w implementacji paginacji, utwórz funkcję, która będzie pobierać dane 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, umożliwiając użytkownikowi eksplorację bieżących danych podczas przewijania, zanim uruchomi się pobieranie kolejnego zestawu danych.
Teraz zdefiniuj hook `useInfiniteQuery`. Po pierwszym zamontowaniu komponentu, hook pobierze pierwszą stronę danych z serwera. Gdy użytkownik przewija stronę w dół, hook automatycznie pobierze kolejną 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 `posts` łączy wszystkie posty z różnych stron w jedną tablicę, tworząc spłaszczoną wersję zmiennej `data`. Umożliwia to łatwe mapowanie i renderowanie poszczególnych postów.
Aby śledzić przewijanie użytkownika i ładować więcej danych, gdy użytkownik jest blisko dołu listy, można zdefiniować funkcję wykorzystującą Intersection Observer API do wykrywania, kiedy elementy przecinają się z widocznym obszarem.
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 dodaj 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ź adres http://localhost:3000/InfiniteScroll, aby zobaczyć działanie nieskończonego przewijania.
TanStack Query: znacznie więcej niż tylko pobieranie danych
Paginacja i nieskończone przewijanie są dobrymi przykładami, które pokazują możliwości TanStack Query. W najprostszych słowach, jest to uniwersalna biblioteka do zarządzania danymi.
Dzięki bogatemu zestawowi funkcji, można usprawnić procesy zarządzania danymi w aplikacji, w tym efektywne zarządzanie stanem. Oprócz obsługi różnych zadań związanych z danymi, można poprawić wydajność aplikacji internetowych, jak również wygodę użytkowania.
newsblog.pl
Maciej – redaktor, pasjonat technologii i samozwańczy pogromca błędów w systemie Windows. Zna Linuxa lepiej niż własną lodówkę, a kawa to jego główne źródło zasilania. Pisze, testuje, naprawia – i czasem nawet wyłącza i włącza ponownie. W wolnych chwilach udaje, że odpoczywa, ale i tak kończy z laptopem na kolanach.