Paginación con botón antes del infinite scroll

Resumen

Antes de llegar al infinite scrolling, necesitas un paso intermedio: una paginación con botón de cargar más que consuma tu API por partes. Aquí aprendes cómo construirla en JavaScript usando Axios y la API de The Movie DB, preparando el terreno para el scroll dinámico que viene después.

¿Cómo crear el botón de cargar más en una sección dinámica?

La idea es insertar un botón al final de la genericSection donde ya se renderizan las películas de tendencias. Ese botón será el disparador para pedir la siguiente página al endpoint.

El código base es directo: creas el elemento, le pones texto y lo agregas al contenedor.

js const btnLoadMore = document.createElement('button'); btnLoadMore.innerHTML = 'Cargar más'; genericSection.appendChild(btnLoadMore);

Un detalle típico que te puede pasar: si nombras la variable btnLoadMore pero luego haces appendChild(button), la consola te grita button is not defined. Revisa siempre que el nombre coincida.

¿Qué es un endpoint paginado? Es una URL de API que acepta un parámetro como page para devolver resultados en bloques. En vez de traer todo de una, pides la página 1, luego la 2, y así.

¿Cómo enviar el query parameter page con Axios?

Axios te deja pasar los query parameters como un objeto dentro de params, sin tener que concatenarlos a la URL manualmente. Es más limpio y se ve más ordenado en JavaScript [04:30].

js const { data } = await api('trending/movie/day', { params: { page: page }, });

La documentación de The Movie DB confirma que casi todos los endpoints (búsquedas, Discover, tendencias) aceptan page como query parameter. Por eso pedir la página 2 te devuelve películas distintas a las de la página 1.

Creas entonces una función nueva, getPaginatedTrendingMovies, que hace la misma llamada pero con el parámetro page dinámico. La asocias al botón con un addEventListener de tipo click.

¿Por qué la función createMovies necesita un parámetro clean?

Al darle clic al botón, la primera vez las películas se reemplazaban en lugar de añadirse abajo. Eso pasa porque createMovies limpia el contenedor con innerHTML = '' antes de insertar nuevos elementos.

La solución es controlar ese comportamiento con un parámetro clean:

  • Por defecto, clean: true para que limpie el contenedor en navegación normal entre rutas.
  • Cuando paginas, le envías clean: false para que conserve las películas anteriores y agregue las nuevas debajo.
  • Lo mismo aplica al lazy loading: lo activas con lazyLoad: true.

Para que el código no quede lleno de true, false, true sin contexto, conviene recibir esos parámetros como un solo objeto con propiedades nombradas.

js function createMovies(movies, container, { lazyLoad = false, clean = true } = {}) { if (clean) container.innerHTML = ''; // ... resto del render }

Este truco de destructuración con valores por defecto y objeto vacío como fallback evita errores si no envías nada y hace los argumentos legibles. Es un patrón que viene de programación orientada a objetos en JavaScript.

¿Por qué usar un objeto en vez de varios parámetros? Porque al llamar la función sabes qué significa cada valor. createMovies(movies, section, { lazyLoad: true, clean: false }) es mucho más claro que createMovies(movies, section, true, false).

¿Cómo cargar páginas infinitas con una variable dinámica?

Un solo clic carga la página 2, pero el botón desaparece y no puedes seguir. La estrategia es regenerar el botón dentro de getPaginatedTrendingMovies después de cada carga, con su propio addEventListener que vuelve a llamar a la misma función.

No es recursividad pura, es dependencia del evento de clic para reinvocar la función.

Para que cada llamada pida una página distinta, declaras una variable page que se incrementa antes de cada solicitud:

js let page = 1;

async function getPaginatedTrendingMovies() { page++; const { data } = await api('trending/movie/day', { params: { page }, }); createMovies(data.results, genericSection, { lazyLoad: true, clean: false });

const btnLoadMore = document.createElement('button'); btnLoadMore.innerHTML = 'Cargar más'; btnLoadMore.addEventListener('click', getPaginatedTrendingMovies); genericSection.appendChild(btnLoadMore); }

Flujo completo:

  1. La carga inicial trae la página 1 con clean: true.
  2. Al hacer clic en cargar más, page pasa a 2 y se piden esos resultados sin limpiar.
  3. Se inserta un nuevo botón al final, listo para pedir la página 3.
  4. El ciclo se repite cuantas veces el usuario quiera.

Un reto extra que puedes intentar: eliminar el botón anterior cada vez que generas uno nuevo, para que solo quede visible el último. Mejora la experiencia y evita botones acumulados en medio de la lista.

Con esta base ya tienes paginación funcional sobre el mismo endpoint, conservando las películas previas y avanzando página por página. El siguiente paso es reemplazar el evento click por un evento de scroll, de modo que al llegar al footer se cargue automáticamente el siguiente bloque. ¿Te animas a intentarlo antes de la próxima clase? Cuéntame en los comentarios cómo lo resolverías.