In this article we’ll see how to fetch data from a REST API with the Fetch API while also handling result pagination.
The Fetch API has a peculiar feature: the Promise returned by calling fetch()
is always set to the resolved
state. This means that to understand whether the HTTP request was successful, we must check the boolean ok
property of the response and throw an exception if its value is false
.
async function getData(url) {
try {
const request = await fetch(url);
if(!request.ok) {
throw new Error('Request error.');
}
return await request.json();
} catch(err) {
return null;
}
}
In this case, we will only have an actual JSON value if the initial request was successful.
We delegate data retrieval with pagination to a specific function:
async function getFacts(page = 1) {
const list = document.getElementById('facts-list');
const preloader = document.getElementById('preloader');
preloader.classList.remove('loaded');
try {
const url = `${API_ENDPOINT}?page=${page}`;
const facts = await getData(url);
if(!facts) {
throw new Error('Request error.');
}
const data = facts.data;
let contents = '';
for(const fact of data) {
contents += `<li>${fact.fact}</li>`;
}
list.innerHTML = contents;
createPaginationLinks(facts.prev_page_url, facts.next_page_url);
preloader.classList.add('loaded');
} catch(err) {
preloader.classList.add('loaded');
list.innerHTML = '<li class="error">No data to show.</li>';
}
}
Pagination data is usually returned as a full URL containing the query string with the page number. In this case prev_page_url
and next_page_url
will contain the URLs of the previous and next pages, or null
if such pages do not exist.
Let’s now add the page navigation buttons:
function createPaginationLinks(previous, next) {
const pagination = document.getElementById('pagination');
pagination.innerHTML = '';
if(previous) {
const previousBtn = document.createElement('button');
previousBtn.type = 'button';
previousBtn.dataset.page = previous;
previousBtn.innerText = 'Previous';
previousBtn.id = 'previous';
previousBtn.className = 'pagination-link';
pagination.appendChild(previousBtn);
}
if(next) {
const nextBtn = document.createElement('button');
nextBtn.type = 'button';
nextBtn.dataset.page = next;
nextBtn.innerText = 'Next';
nextBtn.id = 'next';
nextBtn.className = 'pagination-link';
pagination.appendChild(nextBtn);
}
}
Each button has the page
data attribute where the pagination URL is stored. To handle clicks on these buttons, we must rely on event delegation since the buttons are dynamically updated in the DOM with each request.
function handlePagination() {
document.addEventListener('click', evt => {
if(evt.target.classList.contains('pagination-link')) {
const button = evt.target;
const link = button.dataset.page;
const page = parseInt(link.split('?')[1].replace('page=', ''), 10);
getFacts(page);
}
});
}
Finally, we initialize our code:
document.addEventListener('DOMContentLoaded', () => {
handlePagination();
getFacts();
});
Demo
Conclusion
At a theoretical level, the only potentially problematic technical detail in using the Fetch API concerns handling HTTP exceptions. The rest of the features are definitely more intuitive for the use we want to make of them.