Hur man implementerar oändlig rullning och paginering med Next.js och TanStack Query

De flesta appar du kommer att utveckla kommer att hantera data; allt eftersom programmen fortsätter att skala kan det bli en allt större mängd av det. När applikationer misslyckas med att hantera stora datamängder effektivt presterar de dåligt.

Paginering och oändlig rullning är två populära tekniker som du kan använda för att optimera appens prestanda. De kan hjälpa dig att hantera datarendering mer effektivt och förbättra den övergripande användarupplevelsen.

Paginering och oändlig rullning med TanStack Query

TanStack-fråga—en anpassning av React Query — är ett robust tillståndshanteringsbibliotek för JavaScript-applikationer. Den erbjuder en effektiv lösning för att hantera applikationstillstånd, bland andra funktioner, inklusive datarelaterade uppgifter som cachning.

Paginering innebär att en stor datamängd delas upp i mindre sidor, så att användare kan navigera i innehållet i hanterbara bitar med hjälp av navigeringsknappar. Däremot ger oändlig rullning en mer dynamisk surfupplevelse. När användaren rullar, laddas och visas ny data automatiskt, vilket eliminerar behovet av explicit navigering.

Paginering och oändlig rullning syftar till att effektivt hantera och presentera stora mängder data. Valet mellan de två beror på applikationens datakrav.

Du kan hitta detta projekts kod i denna GitHub förvaret.

Konfigurera ett Next.js-projekt

För att komma igång, skapa ett Next.js-projekt. Installera den senaste versionen av Next.js 13 som använder appkatalogen.

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

Installera sedan TanStack-paketet i ditt projekt med hjälp av npm, Node-pakethanteraren.

 npm i @tanstack/react-query 

Integrera TanStack Query i Next.js-applikationen

För att integrera TanStack Query i ditt Next.js-projekt måste du skapa och initiera en ny instans av TanStack Query i roten av programmet – filen layout.js. För att göra det, importera QueryClient och QueryClientProvider från TanStack Query. Slå sedan in barnens rekvisita med QueryClientProvider enligt följande:

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

Denna inställning säkerställer att TanStack Query har fullständig åtkomst till applikationens tillstånd.

UseQuery-kroken effektiviserar datahämtning och hantering. Genom att tillhandahålla pagineringsparametrar, såsom sidnummer, kan du enkelt hämta specifika delmängder av data.

Dessutom ger kroken olika alternativ och konfigurationer för att anpassa din datahämtningsfunktionalitet, inklusive inställning av cache-alternativ, samt hantering av laddningstillstånd effektivt. Med dessa funktioner kan du enkelt skapa en sömlös pagineringsupplevelse.

Nu, för att implementera paginering i Next.js-appen, skapa en Pagination/page.js-fil i src/app-katalogen. Inuti den här filen gör du följande importer:

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

Definiera sedan en React funktionell komponent. Inuti den här komponenten måste du definiera en funktion som hämtar data från ett externt API. Använd i det här fallet JSONPlaceholder API för att hämta en uppsättning inlägg.

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

  
}

Definiera nu useQuery-kroken och ange följande parametrar som objekt:

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

KeepPreviousData-värdet är sant, vilket säkerställer att appen bevarar tidigare data medan den hämtar ny data. queryKey-parametern är en array som innehåller nyckeln för frågan, i det här fallet, slutpunkten och den aktuella sidan som du vill hämta data för. Slutligen utlöser parametern queryFn, fetchPosts, funktionsanropet för att hämta data.

Som nämnts tidigare ger kroken flera tillstånd som du kan packa upp, liknande hur du skulle destrukturera arrayer och objekt, och använda dem för att förbättra användarupplevelsen (återge lämpliga användargränssnitt) under datahämtningsprocessen. Dessa tillstånd inkluderar isLoading, isError och mer.

För att göra det, inkludera följande kod för att återge olika meddelandeskärmar baserat på det aktuella läget för den pågående processen:

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

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

Slutligen, inkludera koden för JSX-elementen som kommer att återges på webbläsarsidan. Denna kod har även två andra funktioner:

  • När appen hämtar inläggen från API:et kommer de att lagras i datavariabeln som tillhandahålls av useQuery-kroken. Denna variabel hjälper till att hantera programmets tillstånd. Du kan sedan mappa över listan över inlägg som lagras i denna variabel och rendera dem i webbläsaren.
  • För att lägga till två navigeringsknappar, Föregående och Nästa, så att användare kan fråga och visa ytterligare sidnumrerade data i enlighet med detta.
   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>
  );

Slutligen, starta utvecklingsservern.

 npm run dev 

Gå sedan över till http://localhost:3000/Pagination i en webbläsare.

Eftersom du inkluderade pagineringsmappen i appkatalogen, behandlar Next.js den som en rutt, så att du kan komma åt sidan på den webbadressen.

Oändlig rullning ger en sömlös surfupplevelse. Ett bra exempel är YouTube, som hämtar nya videor automatiskt och visar dem när du rullar nedåt.

UseInfiniteQuery-kroken låter dig implementera oändlig rullning genom att hämta data från en server på sidor och automatiskt hämta och rendera nästa sida med data när användaren rullar nedåt.

För att implementera oändlig rullning, lägg till en InfiniteScroll/page.js-fil i src/app-katalogen. Gör sedan följande importer:

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

Skapa sedan en funktionell React-komponent. Inuti denna komponent, liknande pagineringsimplementeringen, skapa en funktion som hämtar inläggens data.

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

  
}

Till skillnad från sidnumreringsimplementeringen introducerar den här koden en två sekunders fördröjning vid hämtning av data så att en användare kan utforska aktuell data medan de rullar för att utlösa en återhämtning av en ny uppsättning data.

Definiera nu useInfiniteQuery-kroken. När komponenten initialt monteras kommer kroken att hämta den första sidan med data från servern. När användaren rullar nedåt kommer kroken automatiskt att hämta nästa sida med data och återge den i komponenten.

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

Inläggsvariabeln kombinerar alla inlägg från olika sidor till en enda array, vilket resulterar i en tillplattad version av datavariabeln. Detta gör att du enkelt kan kartlägga och rendera de enskilda inläggen.

För att spåra användarrullningar och ladda mer data när användaren är nära botten av listan, kan du definiera en funktion som använder Intersection Observer API för att upptäcka när element korsar visningsporten.

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

Slutligen, inkludera JSX-elementen för inläggen som renderas i webbläsaren.

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

När du har gjort alla ändringar, besök http://localhost:3000/InfiniteScroll för att se dem i aktion.

TanStack-fråga: Mer än bara datahämtning

Paginering och oändlig rullning är bra exempel som lyfter fram funktionerna hos TanStack Query. Enkelt uttryckt är det ett allsidigt datahanteringsbibliotek.

Med dess omfattande uppsättning funktioner kan du effektivisera din apps datahanteringsprocesser, inklusive effektiv hantering av tillstånd. Vid sidan av andra datarelaterade uppgifter kan du förbättra den övergripande prestandan för dina webbapplikationer, såväl som användarupplevelsen.