A maioria das aplicações que você irá criar necessitará gerenciar dados, e à medida que os programas se expandem, a quantidade desses dados tende a aumentar. Quando os aplicativos não conseguem administrar grandes volumes de dados de maneira eficiente, seu desempenho é prejudicado.
A paginação e a rolagem infinita são duas técnicas populares que podem ser utilizadas para otimizar o desempenho de uma aplicação. Elas auxiliam no tratamento da renderização de dados de forma mais eficaz e aperfeiçoam a experiência do usuário.
Implementando Paginação e Rolagem Infinita com TanStack Query
TanStack Query, uma adaptação do React Query, é uma biblioteca robusta de gerenciamento de estado para aplicativos JavaScript. Ela oferece uma solução eficaz para administrar o estado da aplicação, além de outras funcionalidades, incluindo tarefas relacionadas a dados, como o uso de cache.
A paginação consiste na divisão de um conjunto grande de dados em páginas menores, permitindo que os usuários naveguem pelo conteúdo em partes gerenciáveis através de botões de navegação. Por outro lado, a rolagem infinita proporciona uma experiência de navegação mais fluida. À medida que o usuário rola a página, novos dados são carregados e exibidos automaticamente, eliminando a necessidade de navegação explícita.
Ambas, paginação e rolagem infinita, têm como objetivo gerenciar e exibir grandes quantidades de dados de forma eficiente. A escolha entre elas dependerá das necessidades específicas de cada aplicação.
O código deste projeto pode ser encontrado neste repositório no GitHub.
Configuração de um Projeto Next.js
Para iniciar, crie um projeto Next.js. Utilize a versão mais recente do Next.js 13, que utiliza o diretório App.
npx create-next-app@latest next-project --app
Em seguida, instale o pacote TanStack em seu projeto usando o npm, o gerenciador de pacotes do Node.
npm i @tanstack/react-query
Integrando o TanStack Query em um Aplicativo Next.js
Para integrar o TanStack Query em seu projeto Next.js, é necessário criar e inicializar uma nova instância do TanStack Query na raiz da aplicação, no arquivo layout.js. Importe QueryClient e QueryClientProvider do TanStack Query e envolva a propriedade `children` com o `QueryClientProvider` da seguinte forma:
"use client"
import React from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';const metadata = {
title: 'Criar Aplicação Next',
description: 'Gerado por 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 };
Essa configuração garante que o TanStack Query tenha acesso completo ao estado da aplicação.
O hook `useQuery` simplifica 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 hook oferece diversas opções e configurações para personalizar a 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 uma experiência de paginação perfeita sem grandes dificuldades.
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ê deve criar uma função que buscará dados de uma API externa. Neste exemplo, usaremos a API JSONPlaceholder para buscar um conjunto de posts.
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('Falha ao buscar posts');
}const data = await response.json();
return data;
} catch (error) {
console.error(error);
throw error;
}
};
}
Agora, defina o hook `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` como verdadeiro 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, que neste caso é o endpoint e a página atual para a qual se deseja buscar os dados. Por fim, o parâmetro `queryFn`, `fetchPosts`, aciona a chamada de função para buscar os dados.
Conforme mencionado anteriormente, o hook oferece diversos estados que podem ser desestruturados, como se faria com arrays e objetos, e usados para melhorar a experiência do usuário (renderizando interfaces apropriadas) durante o processo de busca de dados. Esses estados incluem `isLoading`, `isError`, e outros.
Para isso, inclua o seguinte código para renderizar mensagens diferentes com base no estado atual do processo em andamento:
if (isLoading) {
return (<h2>Carregando...</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 a duas funções adicionais:
- Assim que o aplicativo buscar os posts da API, eles serão armazenados na variável `data` fornecida pelo hook `useQuery`. Esta variável auxilia no gerenciamento do estado do aplicativo. É possível iterar sobre a lista de posts armazenados nessa variável e renderizá-los no navegador.
- Adicionar dois botões de navegação, “Anterior” e “Próximo”, para permitir que os usuários consultem e exibam dados paginados adicionais conforme necessário.
return (
<div>
<h2 className="header">Paginação com 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"
>Página Anterior</button><button
onClick={() => setPage(prevState => prevState + 1)}
className="next-button"
>Próxima Página</button>
</div>
</div>
);
Por fim, inicie o servidor de desenvolvimento.
npm run dev
Em seguida, acesse `http://localhost:3000/Pagination` em um navegador.
Como a pasta `Pagination` foi incluída no diretório do aplicativo, o Next.js a trata como uma rota, permitindo o acesso à página por meio do URL especificado.
A rolagem infinita oferece uma experiência de navegação mais fluida. Um bom exemplo é o YouTube, que busca novos vídeos automaticamente e os exibe conforme o usuário rola a página para baixo.
O hook `useInfiniteQuery` permite implementar a rolagem infinita, buscando dados do servidor em páginas e carregando 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` ao 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, de forma semelhante à implementação da paginação, crie uma função que 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('Falha ao buscar posts');
}const data = await response.json();
await new Promise((resolve) => setTimeout(resolve, 2000));
return data;
} catch (error) {
console.error(error);
throw error;
}
};
}
Diferentemente da implementação da paginação, este código introduz um atraso de dois segundos ao buscar dados, permitindo que o usuário explore os dados atuais enquanto rola a página, acionando a busca por um novo conjunto de dados.
Agora, defina o hook `useInfiniteQuery`. Quando o componente é montado inicialmente, o hook buscará a primeira página de dados do servidor. À medida que o usuário rola para baixo, o hook 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 todos os posts de páginas diferentes em um único array, resultando em uma versão nivelada da variável `data`. Isso permite iterar e renderizar os posts individuais sem dificuldade.
Para monitorar as ações de rolagem do usuário e carregar mais dados quando ele estiver próximo ao final da lista, é possível 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 que os posts sejam renderizados no navegador.
return (
<div>
<h2 className="header">Rolagem Infinita</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 ? 'Buscando...' : isLoadingMore ? 'Carregando mais...' : null}
</div>
</div>
);
Após realizar todas as alterações, visite `http://localhost:3000/InfiniteScroll` para vê-las em funcionamento.
TanStack Query: Mais do que Apenas Busca de Dados
Paginação e rolagem infinita são excelentes exemplos que destacam os recursos do TanStack Query. Simplificando, ela é uma biblioteca completa para gerenciamento de dados.
Com seu amplo conjunto de recursos, ela permite agilizar os processos de gerenciamento de dados de sua aplicação, incluindo o tratamento eficiente do estado. Em conjunto com outras tarefas relacionadas a dados, você pode melhorar o desempenho geral das aplicações web, bem como a experiência do usuário.