Comment implémenter le défilement et la pagination infinis avec Next.js et TanStack Query



La plupart des applications que vous allez concevoir devront manipuler des données. À mesure que ces programmes s’étoffent, la quantité de données à gérer peut croître de manière significative. Si une application ne parvient pas à gérer des volumes de données importants de façon adéquate, ses performances en souffriront.

La pagination et le défilement infini sont deux techniques courantes pour optimiser les performances d’une application. Elles permettent une gestion plus efficiente de l’affichage des données et améliorent l’expérience globale de l’utilisateur.

Pagination et défilement infini avec TanStack Query

TanStack Query, une adaptation de React Query, est une bibliothèque robuste de gestion d’état pour les applications JavaScript. Elle offre une solution efficace pour gérer l’état des applications, ainsi que d’autres fonctionnalités comme la mise en cache des données.

La pagination consiste à diviser un grand ensemble de données en pages plus petites. Cela permet aux utilisateurs de parcourir le contenu par blocs gérables, à l’aide de boutons de navigation. Le défilement infini, quant à lui, propose une navigation plus dynamique. Lorsque l’utilisateur fait défiler la page, de nouvelles données sont automatiquement chargées et affichées, évitant ainsi une navigation manuelle par pages.

La pagination et le défilement infini visent à présenter et gérer efficacement de grandes quantités de données. Le choix entre les deux dépend des besoins spécifiques de l’application.

Vous trouverez le code source de ce projet sur ce dépôt GitHub.

Mise en place d’un projet Next.js

Pour commencer, créez un nouveau projet Next.js. Installez la dernière version de Next.js 13 qui utilise le répertoire « App ».

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

Ensuite, installez le package TanStack dans votre projet en utilisant npm, le gestionnaire de packages Node.

 npm i @tanstack/react-query 

Intégration de TanStack Query dans l’application Next.js

Pour intégrer TanStack Query à votre projet Next.js, vous devez créer et initialiser une nouvelle instance de TanStack Query à la racine de l’application : dans le fichier `layout.js`. Importez `QueryClient` et `QueryClientProvider` depuis TanStack Query. Ensuite, entourez le contenu des enfants avec `QueryClientProvider` comme suit :

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

Cette configuration garantit que TanStack Query a un accès complet à l’état de l’application.

Le hook `useQuery` simplifie la récupération et la gestion des données. En fournissant des paramètres de pagination, tels que les numéros de page, vous pouvez facilement récupérer des sous-ensembles spécifiques de données.

De plus, le hook offre diverses options et configurations pour personnaliser votre processus de récupération de données, y compris la configuration des options de mise en cache et une gestion efficace des états de chargement. Grâce à ces fonctionnalités, vous pouvez créer facilement une expérience de pagination transparente.

Pour implémenter la pagination dans l’application Next.js, créez un fichier `Pagination/page.js` dans le répertoire `src/app`. Dans ce fichier, effectuez les importations suivantes :

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

Ensuite, définissez un composant fonctionnel React. Dans ce composant, vous devez définir une fonction qui récupérera les données d’une API externe. Dans cet exemple, utilisez l’ API JSONPlaceholder pour récupérer un ensemble de publications.

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

  
}

Maintenant, définissez le hook `useQuery` et spécifiez les paramètres suivants sous forme d’objets :

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

La valeur `keepPreviousData` est définie sur `true`, ce qui garantit que, lors de la récupération de nouvelles données, l’application conserve les données précédentes. Le paramètre `queryKey` est un tableau contenant la clé de la requête ; ici, il s’agit du point de terminaison et du numéro de page actuel pour lequel vous souhaitez récupérer les données. Enfin, le paramètre `queryFn`, `fetchPosts`, déclenche l’appel de la fonction pour récupérer les données.

Comme mentionné précédemment, le hook fournit plusieurs états que vous pouvez extraire, de la même manière que vous déstructurez des tableaux et des objets, et utiliser pour améliorer l’expérience utilisateur (en affichant les interfaces utilisateur appropriées) pendant le processus de récupération des données. Ces états incluent `isLoading`, `isError`, etc.

Pour ce faire, incluez le code suivant afin d’afficher différents messages en fonction de l’état actuel du processus :

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

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

Enfin, incluez le code des éléments JSX qui s’afficheront sur la page du navigateur. Ce code a également deux autres fonctions :

  • Une fois que l’application a récupéré les publications de l’API, elles sont stockées dans la variable `data` fournie par le hook `useQuery`. Cette variable permet de gérer l’état de l’application. Vous pouvez ensuite parcourir la liste des publications stockées dans cette variable et les afficher sur le navigateur.
  • Ajouter deux boutons de navigation, « Précédent » et « Suivant », pour permettre aux utilisateurs d’interroger et d’afficher des données paginées supplémentaires.
   return (
    <div>
      <h2 className="header">Pagination Next.js</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"
        >Page précédente</button>

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

Enfin, démarrez le serveur de développement.

 npm run dev 

Ensuite, rendez-vous sur `http://localhost:3000/Pagination` dans un navigateur.

Puisque vous avez inclus le dossier `Pagination` dans le répertoire de l’application, Next.js le considère comme une route, ce qui vous permet d’accéder à la page via cette URL.

Le défilement infini offre une navigation fluide. Un bon exemple est YouTube, qui récupère automatiquement de nouvelles vidéos et les affiche lorsque vous faites défiler la page vers le bas.

Le hook `useInfiniteQuery` vous permet de mettre en œuvre un défilement infini en récupérant les données d’un serveur par pages. Il récupère et affiche automatiquement la page de données suivante lorsque l’utilisateur fait défiler vers le bas.

Pour implémenter le défilement infini, ajoutez un fichier `InfiniteScroll/page.js` dans le répertoire `src/app`. Ensuite, effectuez les importations suivantes :

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

Ensuite, créez un composant fonctionnel React. Dans ce composant, comme lors de l’implémentation de la pagination, créez une fonction qui récupérera les données des publications.

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

  
}

Contrairement à l’implémentation de la pagination, ce code introduit un délai de deux secondes lors de la récupération des données. Cela permet à un utilisateur de parcourir les données actuelles tout en faisant défiler pour déclencher une nouvelle récupération d’un autre ensemble de données.

Maintenant, définissez le hook `useInfiniteQuery`. Lors du montage initial du composant, le hook récupère la première page de données du serveur. À mesure que l’utilisateur fait défiler vers le bas, le hook récupère automatiquement la page de données suivante et l’affiche dans le composant.

   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 variable `posts` combine toutes les publications de différentes pages dans un seul tableau, ce qui donne une version aplatie de la variable `data`. Cela vous permet de parcourir et d’afficher facilement les publications individuelles.

Pour suivre les défilements de l’utilisateur et charger davantage de données lorsque l’utilisateur atteint le bas de la liste, vous pouvez définir une fonction qui utilise l’API Intersection Observer. Cette API détecte quand des éléments croisent la zone d’affichage.

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

Enfin, incluez les éléments JSX pour les publications qui s’afficheront dans le navigateur.

   return (
    <div>
      <h2 className="header">Défilement infini</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 ? 'Chargement...' : isLoadingMore ? 'Chargement en cours...' : null}
      </div>
    </div>
  );

Une fois toutes les modifications terminées, visitez `http://localhost:3000/InfiniteScroll` pour les voir en action.

TanStack Query : bien plus que la récupération de données

La pagination et le défilement infini sont d’excellents exemples illustrant les capacités de TanStack Query. Pour faire simple, il s’agit d’une bibliothèque complète de gestion des données.

Grâce à son ensemble complet de fonctionnalités, vous pouvez simplifier les processus de gestion des données de votre application, y compris une gestion efficace de l’état. En plus d’autres tâches liées aux données, vous pouvez améliorer les performances globales de vos applications web, ainsi que l’expérience utilisateur.