Come implementare lo scorrimento infinito e l’impaginazione con Next.js e TanStack Query

La maggior parte delle app che svilupperai gestiranno i dati; man mano che i programmi continuano a espandersi, la loro quantità può aumentare. Quando le applicazioni non riescono a gestire grandi quantità di dati in modo efficace, le prestazioni sono scarse.

L’impaginazione e lo scorrimento infinito sono due tecniche popolari che puoi utilizzare per ottimizzare le prestazioni dell’app. Possono aiutarti a gestire il rendering dei dati in modo più efficiente e migliorare l’esperienza utente complessiva.

Impaginazione e scorrimento infinito utilizzando TanStack Query

Domanda TanStack—un adattamento di React Query—è una solida libreria di gestione dello stato per applicazioni JavaScript. Offre una soluzione efficiente per la gestione dello stato dell’applicazione, tra le altre funzionalità, comprese le attività relative ai dati come la memorizzazione nella cache.

L’impaginazione prevede la divisione di un set di dati di grandi dimensioni in pagine più piccole, consentendo agli utenti di navigare nel contenuto in blocchi gestibili utilizzando i pulsanti di navigazione. Al contrario, lo scorrimento infinito offre un’esperienza di navigazione più dinamica. Mentre l’utente scorre, i nuovi dati vengono caricati e visualizzati automaticamente, eliminando la necessità di una navigazione esplicita.

L’impaginazione e lo scorrimento infinito mirano a gestire e presentare in modo efficiente grandi quantità di dati. La scelta tra i due dipende dai requisiti di dati dell’applicazione.

Puoi trovare il codice di questo progetto in questo GitHub deposito.

Impostazione di un progetto Next.js

Per iniziare, crea un progetto Next.js. Installa l’ultima versione di Next.js 13 che utilizza la directory dell’app.

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

Successivamente, installa il pacchetto TanStack nel tuo progetto utilizzando npm, il gestore pacchetti Node.

 npm i @tanstack/react-query 

Integra la query TanStack nell’applicazione Next.js

Per integrare TanStack Query nel tuo progetto Next.js, devi creare e inizializzare una nuova istanza di TanStack Query nella root dell’applicazione: il file layout.js. Per fare ciò, importa QueryClient e QueryClientProvider da TanStack Query. Quindi, avvolgi la prop dei bambini con QueryClientProvider come segue:

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

Questa configurazione garantisce che TanStack Query abbia accesso completo allo stato dell’applicazione.

L’hook useQuery semplifica il recupero e la gestione dei dati. Fornendo parametri di impaginazione, come i numeri di pagina, è possibile recuperare facilmente sottoinsiemi di dati specifici.

Inoltre, l’hook fornisce varie opzioni e configurazioni per personalizzare la funzionalità di recupero dei dati, inclusa l’impostazione delle opzioni della cache, nonché la gestione efficiente degli stati di caricamento. Con queste funzionalità, puoi creare facilmente un’esperienza di impaginazione senza interruzioni.

Ora, per implementare l’impaginazione nell’app Next.js, crea un file Pagination/page.js nella directory src/app. All’interno di questo file, effettua le seguenti importazioni:

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

Quindi, definire un componente funzionale React. All’interno di questo componente, devi definire una funzione che recupererà i dati da un’API esterna. In questo caso, utilizzare il API JSONPlaceholder per recuperare una serie di post.

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

  
}

Ora, definisci l’hook useQuery e specifica i seguenti parametri come oggetti:

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

Il valore keepPreviousData è true, il che garantisce che, durante il recupero di nuovi dati, l’app conservi i dati precedenti. Il parametro queryKey è un array contenente la chiave per la query, in questo caso l’endpoint e la pagina corrente per cui desideri recuperare i dati. Infine, il parametro queryFn, fetchPosts, attiva la chiamata alla funzione per recuperare i dati.

Come accennato in precedenza, l’hook fornisce diversi stati che è possibile decomprimere, in modo simile a come destrutturare array e oggetti, e utilizzarli per migliorare l’esperienza dell’utente (rendering delle UI appropriate) durante il processo di recupero dei dati. Questi stati includono isLoading, isError e altro.

A tale scopo, includere il codice seguente per visualizzare diverse schermate di messaggio in base allo stato corrente del processo in corso:

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

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

Infine, includi il codice per gli elementi JSX che verranno visualizzati nella pagina del browser. Questo codice svolge anche altre due funzioni:

  • Una volta che l’app recupera i post dall’API, questi verranno archiviati nella variabile dati fornita dall’hook useQuery. Questa variabile aiuta a gestire lo stato dell’applicazione. Puoi quindi mappare l’elenco dei post archiviati in questa variabile e visualizzarli nel browser.
  • Aggiungere due pulsanti di navigazione, Precedente e Successivo, per consentire agli utenti di eseguire query e visualizzare di conseguenza ulteriori dati impaginati.
   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>
  );

Infine, avvia il server di sviluppo.

 npm run dev 

Quindi, vai su http://localhost:3000/Pagetion in un browser.

Poiché hai incluso la cartella Pagination nella directory dell’app, Next.js la tratta come un percorso, consentendoti di accedere alla pagina da quell’URL.

Lo scorrimento infinito offre un’esperienza di navigazione senza interruzioni. Un buon esempio è YouTube, che recupera automaticamente nuovi video e li visualizza mentre scorri verso il basso.

L’hook useInfiniteQuery consente di implementare lo scorrimento infinito recuperando i dati da un server in pagine e recuperando e visualizzando automaticamente la pagina successiva di dati mentre l’utente scorre verso il basso.

Per implementare lo scorrimento infinito, aggiungi un file InfiniteScroll/page.js nella directory src/app. Quindi, effettua le seguenti importazioni:

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

Successivamente, crea un componente funzionale React. All’interno di questo componente, simile all’implementazione dell’impaginazione, crea una funzione che recupererà i dati dei post.

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

  
}

A differenza dell’implementazione dell’impaginazione, questo codice introduce un ritardo di due secondi durante il recupero dei dati per consentire a un utente di esplorare i dati correnti mentre scorrono per attivare il recupero di un nuovo set di dati.

Ora definisci l’hook useInfiniteQuery. Quando il componente viene inizialmente montato, l’hook preleverà la prima pagina di dati dal server. Mentre l’utente scorre verso il basso, l’hook recupererà automaticamente la pagina successiva di dati e la renderà nel componente.

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

La variabile posts combina tutti i post di pagine diverse in un unico array, risultando in una versione appiattita della variabile data. Ciò ti consente di mappare e visualizzare facilmente i singoli post.

Per tenere traccia degli spostamenti dell’utente e caricare più dati quando l’utente è vicino al fondo dell’elenco, puoi definire una funzione che utilizza l’API Intersection Observer per rilevare quando gli elementi si intersecano con il viewport.

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

Infine, includi gli elementi JSX per i post visualizzati nel browser.

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

Una volta apportate tutte le modifiche, visitare http://localhost:3000/InfiniteScroll per vederle in azione.

Query TanStack: molto più che un semplice recupero di dati

L’impaginazione e lo scorrimento infinito sono buoni esempi che evidenziano le capacità di TanStack Query. In poche parole, è una libreria completa per la gestione dei dati.

Grazie al suo ampio set di funzionalità, puoi semplificare i processi di gestione dei dati della tua app, inclusa la gestione efficiente dello stato. Oltre ad altre attività relative ai dati, puoi migliorare le prestazioni generali delle tue applicazioni web, nonché l’esperienza dell’utente.