Search⌘ K
AI Features

Pagination

Explore how to implement pagination in a Nuxt 3 project using the Pixabay API. Understand handling query parameters for page and per_page, managing state for images, calculating total pages, and creating navigation controls to enhance dynamic data display.

What is pagination?

Pagination is used when we have many values to organize into pages. The user can switch between these pages, and we can also set how many values we want to see on each page. A typical example is an e-commerce store where we may display 20 products per page, and the user can navigate between them.

Pagination can be handled differently depending on where our data is coming from. For our use, when using the Pixabay API, this is handled in the query string. We can pass in how many images per page we want and also the page number to fetch:

Javascript (babel-node)
const { data: images } = await useFetch(
() => `?key=${apiKey}&q=${searchTerm.value}&page=1&per_page=10`,
{
baseURL: baseUrl,
}
);

Pagination with the Pixabay API

The above example shows how we can add both the page and the per_page parameters to the query string. Since we request the first page and 10 images, we will receive images 1–10. Switching this to page two will request images 1120.

By default, useFetch will try to cache the results to avoid making unnecessary extra requests. This means a faster user experience when a previous page is revisited.

Search example

Before we add pagination, we first need a working search example:

export default function useSearch() {
	const displayImages = useDisplayImages();
	const imageData = useImageData();

	const config = useRuntimeConfig()
	const apiKey = config.public.pixabayApiKey;
	const baseUrl = "https://pixabay.com/api/";

	// when term is updated from index.vue, useFetch will re-run
	const searchTerm = ref("");

	const { data: images } = useFetch(
		() => `?key=${apiKey}&q=${searchTerm.value}`,
		{
			baseURL: baseUrl,
		}
	);

	function setImageData() {
		displayImages.value = [];
		imageData.value = images?.value?.hits;
		displayImages.value = images?.value?.hits;
	}
	watch(images, setImageData);

	return { searchTerm };
}
Our Pixabay search code

The above code example has the following:

The composables/useState.js file

This has two constants for the image data which we can share throughout our app:

  • Line 1: The useImageData contains the original, unmodified set of results from the API.

  • Line 2: The useDisplayImages contains a modified set of results we can use later for filtering, etc.

The composables/useSearch.js file

This file contains all of the search-related data and functions to keep the project organized:

  • Lines 1–2: We store the displayImages and imageData from our state.

  • Line 10: The searchTerm will cause useFetch to run when the value changes.

  • Line 14: We watch for the images to change and run the setImageData function. This sets the displayImages and imageData to our state.

The pages/index.vue file

  • Lines 5–7: The searchTerm which is used to fetch images is updated with the handleSearch function. This is set from the input field, which is linked to the userInput value.

  • Lines 19–21: Loop over and display and return images from the API.

Calculating the total number of pages

To make our next and previous page buttons work correctly, we need to first know how many pages we have in total. This is to avoid requesting the next page when there is no more available. We can also use this to display the total number of pages to the user.

Javascript (babel-node)
Math.ceil(images?.value?.total / imagesPerPage.value);

The above code example can be used to calculate the total number of pages. We divide the number of images by the images per page, then round up to the nearest whole number. We also need to store this value inside of the state, and we can do this inside of the useState.js file:

Javascript (babel-node)
export const useImageData = () => useState("image-data", () => []);
export const useDisplayImages = () => useState("display-images", () => []);
// constants to hold the pagination data:
export const useNumberOfPages = () => useState("number-of-pages", () => 0);
export const useImagesPerPage = () => useState("images-per-page", () => 20);
export const useCurrentPageNumber = () => useState("current-page", () => 1);

This is how we could use it inside the useSearch.js file:

Javascript (babel-node)
export default function useSearch() {
const displayImages = useDisplayImages();
const imageData = useImageData();
// 1. Add 3x variables from state:
const numberOfPages = useNumberOfPages();
const imagesPerPage = useImagesPerPage();
const currentPageNumber = useCurrentPageNumber();
const config = useRuntimeConfig()
const apiKey = config.public.pixabayApiKey;
const baseUrl = "https://pixabay.com/api/";
const searchTerm = ref("");
const { data: images } = useFetch(
// 2. Add current page number and image per page to query string
() =>
`?key=${apiKey}&q=${searchTerm.value}&page=${currentPageNumber.value}&per_page=${imagesPerPage.value}`,
{
baseURL: baseUrl,
}
);
function setImageData() {
displayImages.value = [];
imageData.value = images?.value?.hits;
displayImages.value = images?.value?.hits;
// 3. Set the value of number of pages after a new search
numberOfPages.value = Math.ceil(images?.value?.total / imagesPerPage.value);
}
watch(images, setImageData);
return { searchTerm };
}
  • Lines 5–7: We store the image data from our useState.js file.

  • Line 18: The current page and the number of images are added to the query string,

  • Line 29: The number of pages is calculated each time the images change.

The previous or next page will be requested any time the query string changes. The final step is to update these values.

Putting it all together

Extending the previous example, we can see how this will look inside our project:

export default function usePagination() {
	const numberOfPages = useNumberOfPages();
	const currentPageNumber = useCurrentPageNumber();

	function previousPage() {
		if (currentPageNumber.value === 1) return;
		currentPageNumber.value--;
	}
	function nextPage() {
		if (currentPageNumber.value >= numberOfPages.value) return;
		currentPageNumber.value++;
	}
	return { previousPage, nextPage };
}
Complete pagination example

To update the pagination, a new file has been created:

Composables / usePagination.js

  • Lines 5–8: The previousPage function checks to see if the user is on the first page and prevents this from switching to page zero if true.

  • Lines 9–12: The nextPage function also checks if the user is on the last page before moving forward to prevent any errors if no page can be found.

  • Line 13: These two functions are returned so we can trigger them in the index.vue.

Pages / index.vue

  • Lines 3: The previousPage and nextPage functions are imported.

  • Lines 31–36: Two new buttons are added to call the previousPage and nextPage functions.

  • Line 37: We display the currentPageNumber and numberOfPages values.