Como implementar rolagem e paginação infinitas com Next.js e TanStack Query

A maioria dos aplicativos que você desenvolverá gerenciará dados; à medida que os programas continuam a crescer, pode haver uma quantidade cada vez maior deles. Quando os aplicativos não conseguem gerenciar grandes quantidades de dados de maneira eficaz, eles apresentam desempenho insatisfatório.

Paginação e rolagem infinita são duas técnicas populares que você pode usar para otimizar o desempenho do aplicativo. Eles podem ajudá-lo a lidar com a renderização de dados com mais eficiência e aprimorar a experiência geral do usuário.

Paginação e rolagem infinita usando TanStack Query

Consulta TanStack—uma adaptação do React Query — é uma biblioteca robusta de gerenciamento de estado para aplicativos JavaScript. Oferece uma solução eficiente para gerenciar o estado da aplicação, entre outras funcionalidades, incluindo tarefas relacionadas a dados, como cache.

A paginação envolve a divisão de um grande conjunto de dados em páginas menores, permitindo que os usuários naveguem pelo conteúdo em partes gerenciáveis ​​usando botões de navegação. Por outro lado, a rolagem infinita proporciona uma experiência de navegação mais dinâmica. À medida que o usuário rola, novos dados são carregados e exibidos automaticamente, eliminando a necessidade de navegação explícita.

A paginação e a rolagem infinita visam gerenciar e apresentar com eficiência grandes quantidades de dados. A escolha entre os dois depende dos requisitos de dados do aplicativo.

Você pode encontrar o código deste projeto neste GitHub repositório.

Configurando um projeto Next.js

Para começar, crie um projeto Next.js. Instale a versão mais recente do Next.js 13 que usa o diretório App.

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

Em seguida, instale o pacote TanStack em seu projeto usando npm, o gerenciador de pacotes Node.

 npm i @tanstack/react-query 

Integre a consulta TanStack no aplicativo Next.js

Para integrar o TanStack Query em seu projeto Next.js, você precisa criar e inicializar uma nova instância do TanStack Query na raiz do aplicativo – o arquivo layout.js. Para fazer isso, importe QueryClient e QueryClientProvider do TanStack Query. Em seguida, envolva a propriedade infantil com QueryClientProvider da seguinte maneira:

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

Esta configuração garante que o TanStack Query tenha acesso completo ao estado do aplicativo.

  Qual é a diferença entre as CPUs Intel Core i3, i5, i7 e X?

O gancho useQuery agiliza a busca e o gerenciamento de dados. Ao fornecer parâmetros de paginação, como números de página, você pode recuperar facilmente subconjuntos específicos de dados.

Além disso, o gancho fornece várias opções e configurações para personalizar sua funcionalidade de busca de dados, incluindo a configuração de opções de cache, bem como o tratamento eficiente dos estados de carregamento. Com esses recursos, você pode criar sem esforço uma experiência de paginação perfeita.

Agora, para implementar a paginação no aplicativo Next.js, crie um arquivo Pagination/page.js no diretório src/app. Dentro deste arquivo, faça as seguintes importações:

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

Em seguida, defina um componente funcional React. Dentro deste componente, você precisa definir uma função que irá buscar dados de uma API externa. Neste caso, use o API JSONPlaceholder para buscar um conjunto de postagens.

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

  
}

Agora, defina o gancho useQuery e especifique os seguintes parâmetros como objetos:

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

O valor keepPreviousData é verdadeiro, o que garante que, ao buscar novos dados, o aplicativo preserve os dados anteriores. O parâmetro queryKey é um array que contém a chave da consulta, neste caso, o endpoint e a página atual para a qual você deseja buscar dados. Por último, o parâmetro queryFn, fetchPosts, aciona a chamada de função para buscar dados.

Conforme mencionado anteriormente, o gancho fornece vários estados que você pode descompactar, semelhante a como você desestruturaria arrays e objetos, e utilizá-los para melhorar a experiência do usuário (renderizando UIs apropriadas) durante o processo de busca de dados. Esses estados incluem isLoading, isError e muito mais.

  Deepfakes de áudio: alguém pode dizer se eles são falsos?

Para fazer isso, inclua o seguinte código para renderizar diferentes telas de mensagens com base no estado atual do processo em andamento:

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

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

Por fim, inclua o código dos elementos JSX que serão renderizados na página do navegador. Este código também serve duas outras funções:

  • Assim que o aplicativo buscar as postagens da API, elas serão armazenadas na variável de dados fornecida pelo gancho useQuery. Esta variável ajuda a gerenciar o estado do aplicativo. Você pode então mapear a lista de postagens armazenadas nesta variável e renderizá-las no navegador.
  • Para adicionar dois botões de navegação, Anterior e Próximo, para permitir que os usuários consultem e exibam dados paginados adicionais de acordo.
   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>
  );

Por último, inicie o servidor de desenvolvimento.

 npm run dev 

Em seguida, acesse http://localhost:3000/Pagination em um navegador.

Como você incluiu a pasta Pagination no diretório do aplicativo, Next.js a trata como uma rota, permitindo acessar a página naquele URL.

A rolagem infinita oferece uma experiência de navegação perfeita. Um bom exemplo é o YouTube, que busca novos vídeos automaticamente e os exibe conforme você rola para baixo.

O gancho useInfiniteQuery permite implementar a rolagem infinita buscando dados de um servidor em páginas e buscando e renderizando automaticamente a próxima página de dados conforme o usuário rola para baixo.

Para implementar a rolagem infinita, adicione um arquivo InfiniteScroll/page.js no diretório src/app. Em seguida, faça as seguintes importações:

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

A seguir, crie um componente funcional React. Dentro deste componente, semelhante à implementação da paginação, crie uma função que irá buscar os dados dos posts.

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

  
}

Ao contrário da implementação de paginação, este código introduz um atraso de dois segundos ao buscar dados para permitir que um usuário explore os dados atuais enquanto rola para acionar uma nova busca de um novo conjunto de dados.

  Como instalar extensões do Gnome Shell de maneira fácil com o Extension Manager

Agora, defina o gancho useInfiniteQuery. Quando o componente é montado inicialmente, o gancho buscará a primeira página de dados do servidor. À medida que o usuário rola para baixo, o gancho busca automaticamente a próxima página de dados e a renderiza no 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) : [];

A variável posts combina todas as postagens de páginas diferentes em um único array, resultando em uma versão nivelada da variável data. Isso permite mapear e renderizar facilmente as postagens individuais.

Para rastrear as rolagens do usuário e carregar mais dados quando o usuário está próximo ao final da lista, você pode definir uma função que utiliza a API Intersection Observer para detectar quando os elementos se cruzam com a janela de visualização.

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

Por último, inclua os elementos JSX para as postagens renderizadas no navegador.

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

Depois de fazer todas as alterações, visite http://localhost:3000/InfiniteScroll para vê-las em ação.

Consulta TanStack: mais do que apenas busca de dados

Paginação e rolagem infinita são bons exemplos que destacam os recursos do TanStack Query. Simplificando, é uma biblioteca completa de gerenciamento de dados.

Com seu amplo conjunto de recursos, você pode agilizar os processos de gerenciamento de dados do seu aplicativo, incluindo o tratamento eficiente de estado. Juntamente com outras tarefas relacionadas a dados, você pode melhorar o desempenho geral de suas aplicações web, bem como a experiência do usuário.