Як реалізувати нескінченне прокручування та розбиття на сторінки за допомогою Next.js і TanStack Query

Більшість програм, які ви розроблятимете, керуватимуть даними; оскільки програми продовжують масштабуватися, його кількість може зростати. Коли програми не можуть ефективно керувати великими обсягами даних, вони працюють погано.

Розбивка на сторінки та нескінченне прокручування – два популярні прийоми, які можна використовувати для оптимізації продуктивності програми. Вони можуть допомогти вам ефективніше обробляти дані та покращити загальну взаємодію з користувачем.

Пагінація та нескінченна прокрутка за допомогою запиту TanStack

Запит TanStack— адаптація React Query — це надійна бібліотека керування станом для програм JavaScript. Він пропонує ефективне рішення для керування станом програми, серед інших функцій, включаючи завдання, пов’язані з даними, такі як кешування.

Розбивка на сторінки передбачає поділ великого набору даних на менші сторінки, що дозволяє користувачам переміщатися вмістом у керованих фрагментах за допомогою кнопок навігації. Навпаки, нескінченне прокручування забезпечує більш динамічний досвід перегляду. Коли користувач прокручує, нові дані завантажуються та відображаються автоматично, усуваючи потребу в явній навігації.

Розбивка на сторінки та нескінченне прокручування призначені для ефективного керування та представлення великих обсягів даних. Вибір між ними залежить від вимог програми до даних.

Ви можете знайти код цього проекту тут GitHub сховище.

Налаштування проекту Next.js

Для початку створіть проект Next.js. Установіть останню версію Next.js 13, яка використовує каталог App.

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

Далі встановіть пакет TanStack у свій проект за допомогою npm, менеджера пакетів Node.

 npm i @tanstack/react-query 

Інтегруйте запит TanStack у додаток Next.js

Щоб інтегрувати TanStack Query у ваш проект Next.js, вам потрібно створити та ініціалізувати новий екземпляр TanStack Query у корені програми — файл layout.js. Для цього імпортуйте QueryClient і QueryClientProvider із TanStack Query. Потім оберніть дочірній проп QueryClientProvider наступним чином:

 "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 };

Це налаштування гарантує, що TanStack Query має повний доступ до стану програми.

Хук useQuery спрощує вибірку даних і керування ними. Надаючи параметри розбиття на сторінки, наприклад номери сторінок, ви можете легко отримати певні підмножини даних.

Крім того, хук надає різні параметри та конфігурації для налаштування ваших функцій отримання даних, включаючи налаштування параметрів кешу, а також ефективну обробку станів завантаження. Завдяки цим функціям ви можете без особливих зусиль створити бездоганний досвід розбиття на сторінки.

Тепер, щоб реалізувати розбиття сторінок у програмі Next.js, створіть файл Pagination/page.js у каталозі src/app. У цьому файлі виконайте такі імпорти:

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

Потім визначте функціональний компонент React. Усередині цього компонента вам потрібно визначити функцію, яка отримуватиме дані із зовнішнього API. У цьому випадку використовуйте JSONPlaceholder API щоб отримати набір дописів.

 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;
    }
  };

  
}

Тепер визначте хук useQuery та вкажіть такі параметри як об’єкти:

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

Значення keepPreviousData має значення true, що гарантує, що під час отримання нових даних програма зберігає попередні дані. Параметр queryKey — це масив, що містить ключ для запиту, у цьому випадку кінцеву точку та поточну сторінку, дані для якої потрібно отримати. Нарешті, параметр queryFn, fetchPosts, запускає виклик функції для отримання даних.

Як згадувалося раніше, хук надає кілька станів, які ви можете розпакувати, подібно до того, як ви деструктуруєте масиви та об’єкти та використовуєте їх для покращення взаємодії з користувачем (відтворення відповідних інтерфейсів користувача) під час процесу отримання даних. Ці стани включають isLoading, isError тощо.

Для цього додайте наступний код для відтворення різних екранів повідомлень на основі поточного стану поточного процесу:

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

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

Нарешті, додайте код для елементів JSX, які відображатимуться на сторінці браузера. Цей код також виконує дві інші функції:

  • Після того, як програма отримає повідомлення з API, вони будуть збережені в змінній даних, наданій хуком useQuery. Ця змінна допомагає керувати станом програми. Потім ви можете відобразити список публікацій, що зберігаються в цій змінній, і відобразити їх у браузері.
  • Щоб додати дві навігаційні кнопки, Назад і Далі, щоб дозволити користувачам запитувати та відображати додаткові дані з розбивкою на сторінки відповідно.
   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>
  );

Нарешті, запустіть сервер розробки.

 npm run dev 

Потім перейдіть до http://localhost:3000/Pagination у браузері.

Оскільки ви включили папку Pagination у каталог програми, Next.js розглядає її як маршрут, що дозволяє отримати доступ до сторінки за цією URL-адресою.

Нескінченне прокручування забезпечує плавний перегляд. Гарним прикладом є YouTube, який автоматично отримує нові відео та відображає їх під час прокручування вниз.

Хук useInfiniteQuery дозволяє реалізувати нескінченне прокручування шляхом отримання даних із сервера на сторінках і автоматичного отримання та відтворення наступної сторінки даних, коли користувач прокручує вниз.

Щоб застосувати нескінченне прокручування, додайте файл InfiniteScroll/page.js у каталог src/app. Потім зробіть такі імпорти:

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

Далі створіть функціональний компонент React. Усередині цього компонента, подібно до реалізації пагінації, створіть функцію, яка буде отримувати дані публікацій.

 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;
    }
  };

  
}

На відміну від реалізації розбиття на сторінки, цей код вводить двосекундну затримку під час отримання даних, щоб дозволити користувачеві досліджувати поточні дані під час прокручування, щоб ініціювати повторне отримання нового набору даних.

Тепер визначте хук useInfiniteQuery. Коли компонент спочатку монтується, хук отримає першу сторінку даних із сервера. Коли користувач прокручує вниз, хук автоматично отримує наступну сторінку даних і відтворює її в компоненті.

   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) : [];

Змінна posts об’єднує всі публікації з різних сторінок в єдиний масив, у результаті чого створюється зведена версія змінної data. Це дозволяє легко наносити на карту та відтворювати окремі публікації.

Щоб відстежувати прокручування користувача та завантажувати більше даних, коли користувач знаходиться близько до кінця списку, ви можете визначити функцію, яка використовує Intersection Observer API для виявлення, коли елементи перетинаються з вікном перегляду.

   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]);

Нарешті, додайте елементи JSX для публікацій, які відображаються в браузері.

   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>
  );

Після внесення всіх змін відвідайте http://localhost:3000/InfiniteScroll, щоб побачити їх у дії.

Запит TanStack: більше, ніж просто отримання даних

Розбивка на сторінки та нескінченне прокручування є хорошими прикладами, які підкреслюють можливості TanStack Query. Простіше кажучи, це універсальна бібліотека для керування даними.

Завдяки широкому набору функцій ви можете оптимізувати процеси керування даними свого додатка, зокрема ефективну обробку стану. Поряд з іншими завданнями, пов’язаними з даними, ви можете покращити загальну продуктивність своїх веб-додатків, а також покращити взаємодію з користувачем.