dev.ansango / blog

Cómo implementar scroll infinito con Svelte y TanStack Query

· 3 min de lectura

Cómo implementar scroll infinito con Svelte y TanStack Query

El scroll infinito es una técnica muy utilizada en aplicaciones modernas para cargar contenido dinámico a medida que el usuario se desplaza. En este artículo, te mostraré cómo implementarlo usando SvelteTanStack Query y un pequeño hook personalizado para detectar la intersección de elementos.

Contexto Del proyecto

La funcionalidad permite cargar una lista de lanzamientos musicales de manera progresiva, utilizando la librería TanStack Query para gestionar el estado de las peticiones. Además, empleamos un hook useInfiniteScroll para detectar cuando el usuario llega al final del contenido visible.

Configuración De useInfiniteGetReleases

Este hook basado en TanStack Query realiza peticiones a una API para recuperar datos paginados. Configuramos el comportamiento del scroll infinito con los siguientes pasos clave:

  • Función de query (queryFn): Solicita datos desde la API según el número de página.
  • Parámetro para la siguiente página (getNextPageParam): Define la lógica para determinar si hay más contenido disponible.
  • Selección de datos (select): Normaliza y simplifica la estructura de los datos antes de exponerlos al componente.
import { releaseQueryClient } from '$lib/global-query-client/release';
import { createInfiniteQuery } from '@tanstack/svelte-query';

const { getReleases } = releaseQueryClient();

export const useInfiniteGetReleases = (perPage?: number) =>
	createInfiniteQuery({
		queryKey: ['releases'],
		queryFn: ({ pageParam }) => getReleases.queryFn(String(pageParam), perPage),
		initialPageParam: 1,
		getNextPageParam: ({ pagination }) => {
			return pagination.urls.next ? pagination.page + 1 : undefined;
		},
		select: (data) => {
			return {
				pages: data.pages.map((page) => {
					return {
						releases: page.releases.map((release) => {
							return {
								title: release.basic_information.title,
								artist: release.basic_information.artists[0].name,
								cover_image: release.basic_information.cover_image,
								id: release.id
							};
						})
					};
				}),
				pageParams: data.pageParams
			};
		},
		staleTime: 1000 * 60 * 10
	});

Detectando El scroll infinito

El hook useInfiniteScroll utiliza la API de IntersectionObserver para ejecutar una acción cada vez que un elemento es visible en la pantalla. Es perfecto para detectar cuándo se debe cargar más contenido.

type Params = {
	action: () => void;
	element: HTMLElement;
};
export const useInfiniteScroll = () => (params: Params) => {
	const { action, element } = params;
	if (element) {
		const observer = new IntersectionObserver(
			(entries) => {
				const first = entries[0];
				if (first.isIntersecting) {
					action();
				}
			},
			{ threshold: 1 }
		);
		observer.observe(element);
	}
};

Integración En el componente Svelte

El componente principal utiliza estos hooks para implementar el scroll infinito. Aquí está el código explicado paso a paso:

<script lang="ts">
	import { useInfiniteGetReleases, useInfiniteScroll } from '$lib/hooks';
	const query = useInfiniteGetReleases(12);
	const infiniteScroll = useInfiniteScroll();

	let elementRef: HTMLElement;
	// Activar el scroll infinito cuando se cumplan las condiciones
	$: {
		if (
			elementRef &&
			$query.hasNextPage &&
			!$query.isLoading &&
			!$query.isFetchingNextPage &&
			!$query.isFetching
		) {
			infiniteScroll({
				action: () => {
					setTimeout(() => $query.fetchNextPage(), 1500);
				},
				element: elementRef
			});
		}
	}
</script>

<section class="space-y-2">
	<!-- Renderizar la lista de lanzamientos -->
	<ul class="grid grid-cols-12 gap-1 md:gap-2">
		{#if $query.data}
			{#each $query.data.pages as { releases }}
				{#each releases as release}
					<li class="col-span-4 md:col-span-3 lg:col-span-2">
						<a href={`/release/${release.id}`} class="">
							<img
								class="aspect-square object-sm w-full rounded-sm object-cover"
								src={release.cover_image}
								alt={release.title + ' by ' + release.artist}
							/>
						</a>
					</li>
				{/each}
			{/each}
		{/if}
	</ul>
	<!-- Elemento para observar el scroll -->
	<div bind:this={elementRef}>
		{#if $query.isFetching && $query.hasNextPage}
			<ul class="grid grid-cols-12 gap-1 md:gap-2">
				{#each new Array(12) as _, i}
					<li class="col-span-4 md:col-span-3 lg:col-span-2">
						<div class="aspect-square w-full animate-pulse rounded-sm bg-neutral-300"></div>
					</li>
				{/each}
			</ul>
		{/if}
	</div>
</section>
  • Condiciones del scroll infinito: Observamos elementRef solo cuando hay más contenido disponible y las peticiones actuales han finalizado.
  • Elementos visuales: Mostramos un “loader” animado mientras se recuperan datos.

Conclusión

Esta solución combina la gestión de datos eficiente de TanStack Query con la reactividad de Svelte para crear un scroll infinito fluido. El uso de IntersectionObserver asegura un rendimiento óptimo al manejar las actualizaciones del DOM solo cuando es necesario.

¡Implementa este patrón en tus proyectos y mejora la experiencia de usuario! 🚀