Saltar al contenido

La paginación siempre está de moda, sea un sistema de paginación tradicional, con scrollUp o scroll infinito. En esta entrada vamos a ver cómo crear una paginación en Vue 3 con efecto ScrollUp, para ello nos apoyaremos en las $refs, verás que todo es muy sencillo y tiene mucho sentido.

Para simular un ejemplo lo más real posible haremos uso de una API externa que nos ofrece resultados paginados, ideales para realizar este tipo de tutoriales. Instalar dependencias Lo primero que necesitamos hacer es instalar las dependencias necesarias, en este caso Axios, que aunque también vamos a utilizar TailwindCSS, ya lo explicamos aquí.

Para instalar Axios simplemente ejecuta el comando yarn add axios o npm install axios --save y lo tendrás listo. Variables de entorno Aunque no es un requisito, siempre es buena idea centralizar las variables de entorno en un archivo .env, así, cuando cambiemos de entorno simplemente podremos modificar su contenido en lugar del código, una muy mala práctica.

Así que crea tu archivo .env en la raíz de tu proyecto y añade la URL de la API que vamos a utilizar para nuestro sistema de paginación.

VUE_APP_PAGINATION_END_POINT=https://api.instantwebtools.net/v1/passenger

Composable usePagination Ahora es momento de crear nuestra función compositora, la cual fácilmente podemos extraer a otros proyectos con el consiguiente beneficio. Así que crea un archivo en el directorio src/composables llamado usePagination.js y añade el siguiente código que pasamos a explicar.

import { reactive, computed, toRefs } from 'vue'
import axios from 'axios'

export default function () {
  const endpoint = process.env.VUE_APP_PAGINATION_END_POINT
  const pagination = reactive({
    loaded: false,
    containerRef: null,
    page: 0,
    totalPages: 0,
    limit: 20,
    results: []
  })

  const paginate = async () => {
    try {
      pagination.loaded = false
      const { data } = await axios(`${endpoint}?page=${pagination.page}&size=${pagination.limit}`)
      pagination.results = data.data
      pagination.totalPages = data.totalPages
      const top = pagination.containerRef.offsetTop
      window.scrollTo({
        top,
        behavior: 'smooth'
      })
    } catch (e) {
      console.log(e)
    } finally {
      pagination.loaded = true
    }
  }

  const setPage = async (isNext = true) => {
    const currentPage = pagination.page
    const totalPages = pagination.totalPages
    if (isNext) {
      if (currentPage < totalPages) {
        pagination.page = pagination.page + 1
      }
    } else {
      if (currentPage > 0) {
        pagination.page = pagination.page - 1
      }
    }
    await paginate()
  }

  const isFirstPage = computed(() => pagination.page === 0)
  const isLastPage = computed(() => pagination.page === pagination.totalPages)

  return {
    ...toRefs(pagination),
    isFirstPage,
    isLastPage,
    setPage,
    paginate
  }
}

Tenemos datos reactivos para gestionar la información de la paginación actual, un método paginate que se encarga de hacer peticiones a la API con Axios, otro método setPage para definir la página actual y un par de datos calculados con computed que nos permitirán saber si estamos en la primera o en la última página, no necesitamos nada más.

Es importante fijarse también que dentro de pagination tenemos un dato llamado containerRef, esta es la referencia que utilizaremos para gestionar el scrollUp, fíjate en cómo lo utilizamos en el método paginate junto con el comportamiento (behaviour) smooth, con esto simplemente le añadimos una pequeña animación al scroll, nada más. Componente Paginación en Vue 3 Ahora que ya tenemos listo nuestro composable, vamos a crear el componente Pagination.vue dentro del directorio components y añadimos el siguiente código, con lo que ya lo tendremos todo listo.

<template>
  <section class="text-gray-600 body-font">
    <div class="container px-5 py-24 mx-auto">
      <div class="flex flex-col text-center w-full mb-20">
        <h1 class="sm:text-3xl text-2xl font-medium title-font mb-4 text-gray-900">Paginación en Vue 3 con Scroll Up</h1>
        <p class="lg:w-2/3 mx-auto leading-relaxed text-base">
          Un sistema de paginación muy utilizado en la web
        </p>
      </div>
      <div :class="{'loading': !loaded, 'loaded': loaded}">
        <!-- Pagination container -->
        <div class="flex flex-wrap -m-5" ref="containerRef">
          <!-- Pagination items -->
          <div class="p-2 -mb-3 w-full" v-for="passenger in results" :key="passenger._id">
            <div class="h-full flex items-center border-gray-200 border p-4 rounded-lg">
              <img
                :alt="passenger.name"
                class="w-24 h-4"
                :src="passenger.airline.logo"
              />
              <div class="flex-grow ml-6">
                <h2 class="text-gray-900 title-font font-medium">{{ passenger.name }}</h2>
                <p class="text-gray-500">Viajes: {{ passenger.trips }}</p>
              </div>
            </div>
          </div>
          <!-- ./Pagination items -->

          <!-- Pagination buttons -->
          <div class="flex flex-col text-center w-full mt-5">
            <div>
              <div class="flex justify-center">
                <button v-if="!isFirstPage" @click="setPage(false)" class="inline-flex text-white text-gray-700 border-0 py-2 px-6 focus:outline-none hover:bg-gray-200 rounded text-lg">
                  <svg class="h-6 w-6 mr-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
                  </svg>
                </button>
                <button v-if="!isLastPage" @click="setPage(true)" class="ml-4 inline-flex text-white text-gray-700 border-0 py-2 px-6 focus:outline-none hover:bg-gray-200 rounded text-lg">
                  <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
                  </svg>
                </button>
              </div>
            </div>
          </div>
          <!-- ./Pagination buttons -->
        </div>
        <!-- ./Pagination container -->
      </div>
    </div>
  </section>
</template>

<script>
import { onMounted } from 'vue'
import usePagination from '@/composables/usePagination'

export default {
  name: 'Pagination',
  setup () {
    const { results, setPage, paginate, containerRef, isFirstPage, isLastPage, loaded } = usePagination()
    onMounted(() => {
      paginate()
    })
    return {
      containerRef,
      results,
      setPage,
      isFirstPage,
      isLastPage,
      loaded
    }
  }
}
</script>

<style>
.loading {
  opacity: 0.3;
}

.loaded {
  opacity: 1;
}
</style>

La primera vez que el componente se carga y utilizando el hook onMounted hacemos la primera llamada al método paginate, así conseguimos los datos iniciales.

Importante, fíjate en cómo utilizamos ref con containerRef para asignar nuestra referencia, así el navegador sabrá dónde dejarnos al finalizar la paginación.

También puedes ver que dependiendo de si hemos finalizado la carga de datos mostramos una clases css u otra, así, añadiendo un poco de opacidad al contenedor, el usuario final sabe que estamos haciendo cosas, de cara a la UX queda bien, pero se puede mejorar mucho.

Rutas de aprendizaje