There are many ways to paginate your WordPress archives. paginate_links()
function is one of them. It will return the pagination number links for your archives. However this pagination will show posts in separate pages like 1, 2, 3 etc. But there is a way to load more posts in your archive without refreshing the entire page. We shall learn the most effective way to do that with simple JavaScript fetch API. This tutorial covers the entire process of using the fetch API and without any jQuery dependency.
Start with the WordPress loop
The most basic setup for the index.php or archive.php file in WordPress is simple. The main while()
loop will fetch most recent posts in chronological format. Here is the example code that we shall be using in our archives and index page.
<?php
get_header();
if (have_posts()) :
global $wp_query;
?>
<div class="posts-wrapper">
<?php while (have_posts()) : the_post(); ?>
<article <?php post_class(); ?>>
<?php
the_post_thumbnail('medium');
the_title('<h2>','</h2>');
the_excerpt();
?>
</article>
<?php endwhile; ?>
</div>
<?php
//default WordPress pagination links
echo paginate_links();
//load more button for JS fetch API
printf(
'<div class="load-more" data-totalpages="%s"><button id="load_more_posts">%s</button></div>',
esc_attr( $wp_query->max_num_pages ),
esc_html__('Load More','text-doamin')
);
endif;
get_sidebar();
get_footer();
?>
This is a basic WordPress loop. But I must point towards couple of things that we shall be targeting while coding in JavaScript. The div with class .posts-wrapper
is our target container and article.post
is our individual post container.
Load more button
In our above code, we have added a button for the load more event. If you notice, we have passed the total number of available pages in a HTML attribute data-totalpages
which will be used in our JS later. That’s it for the initial setup of the index.php and archive.php file. This will show posts based on the reading settings for number of posts per page.
Create and enqueue the JS file
Now we shall create a new JS file inside our theme’s folder. Open a text editor and add new file and save it as fetch-posts.js inside your JS folder like this –
theme's root folder
-- js (folder)
-- fetch-posts.js (file)
We shall enqueue the JS file from our theme’s function.php. Please use the code below –
function enqueue_scripts(){
//enqueue the fetch-posts js for archives and home only
if( is_archive() || is_home() ){
wp_enqueue_script('fetch-posts', get_theme_file_uri('/js/fetch-posts.js'), NULL, '', true );
}
}
add_action( 'wp_enqueue_scripts', 'enqueue_scripts', 10 );
So we have setup all for the JS. Before moving to the JS code, let’s check couple of things –
- Make sure you have pagination by checking total posts and posts per page from reading settings.
- If there is pagination available, you can see the page links just above the load more button.
You can check if pagination is working properly or not. However, if you have a pretty permalink settings, you can check pagination by adding ?paged=2
to the end of the page url on address bar. If you have plain permalink settings, you can check it by adding &paged=2
to the end of the page url.
The fetch API
The Fetch API provides an interface for fetching resources (including across the network). It will seem familiar to anyone who has used XMLHttpRequest
, but the new API provides a more powerful and flexible feature set.
Concepts and usages of the JavaScript fetch API
Fetch provides a generic definition of Request
and Response
objects (and other things involved with network requests). The fetch()
method takes one mandatory argument, the path to the resource you want to fetch. It returns a Promise
that resolves to the Response
to that request, whether it is successful or not. Once a Response
is retrieved, there are a number of methods available to define what the body content is and how it should be handled.
A basic fetch request is really simple to set up. Have a look at the following code:
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => console.log(data));
You may be interested to find more about fetch()
method and it’s usages here – Using Fetch. It is very simple and powerful method for fetching JSON data or any text data from a given url. Our pagination URL will be providing us HTML. So we need to convert it to string with text()
method instead of json()
method. Later we will convert that to HTML nodes by using DOMParser()
and retrieve our post data from it.
In this process, we shall use the async
& await
keyword and then fetch()
. Like the code below –
async function x(){
let data = await ( await fetch( Url ).catch( errorHandle ) ).text()
console.log(data)
}
function errorHandle(err){
console.log(err)
}
An async function is a function declared with the async
keyword. Async functions are instances of the AsyncFunction
constructor, and the await
keyword is permitted within them. The async
and await
keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains. You may find more on async here.
Our code snippet
Now that we have basic understanding of the JavaScript fetch API and async function, we shall move forward to our actual JS coding. Please open the fetch-posts.js file in a text editor like vscode. Add the following code to it and save.
/**
* Load more function for WordPress archives and category pages
* Works on all types of permalinks
*/
//set all initial variables
let postContainer = document.querySelector('.posts-wrapper'),
loadMore_btn = document.querySelector('#load_more_posts'),
totalPages = document.querySelector('.load-more').getAttribute('data-totalpages'),
currentPage = 1,
url = document.location.href,
fetchUrl = url.match(/[?]/) ? url + '&paged=' : url + '?paged=',//checking permalink structure
parser = new DOMParser();//Initialize the DOM parser
//console.log(totalPages, url)
if( currentPage == totalPages ) {
//run if there is only 1 page
loadMore_btn.classList.add('end-page')
loadMore_btn.disabled = true
loadMore_btn.innerHTML = 'No more posts'
}else if( currentPage < totalPages ){
//run if there is more than 1 page
loadMore_btn.disabled = false
//add event listner for the load more button
loadMore_btn.addEventListener( 'click', loadMorePosts )
}
/**
* @callback loadMorePosts
* async function for the fetch function with await
*/
async function loadMorePosts(){
//increment page number for each click
currentPage++
//change button text while loading
this.innerHTML = 'Loading...'
//fetch function and returned data
let data = await ( await fetch( fetchUrl + currentPage ).catch( errorHandle ) ).text(),
//use DOMParser to convert text string to HTML nodes
htmlData = parser.parseFromString( data, 'text/html' ),
//select posts that will be appended
posts = htmlData.querySelectorAll('article.post')
for( let i = 0; i<posts.length; i++ ){
//initially add 'hide' class to the posts for fadein effect
posts[i].classList.add('hide')
//then append it to the container
postContainer.append( posts[i] )
}
/*********** fadein effect ***********/
//select all hidden posts
let hiddenPosts = postContainer.querySelectorAll('.hide'),
//set time out and make the posts visible
//css transition will fade in
timer = setTimeout( function(){
Array.prototype.map.call( hiddenPosts, function(el){
el.classList.remove('hide')
el.classList.add('show')
})
}, 300)
/*********** fadein effect ends ***********/
if( currentPage == totalPages ){
//if there is no more page,
//disable the button
this.classList.add('end-page')
this.disabled = true
this.innerHTML = 'No more posts'
}else{
//if there is more pages,
//change the button text
this.innerHTML = 'Load More'
}
}
/**
* @callback errorHandle
* @param err to handle errors
*/
function errorHandle(err){
console.warn(err)
}
In the above code snippet, I have used related comments and variable names for better understanding about what is going on exactly. It’s more human readable now. If you have basic concepts of JavaScript, you will be able to grasp this well.
CSS for the fade-in effect and load more button
As you read the code, you should see that we have appended the posts with .hide
class and then with a timeout, we have removed the .hide
class and added .show
class to the appended posts. Now, we can write some CSS for these class and the fade-in effect will be visible. Let’s add the following code to your style.css file.
.load-more{
text-align: center;
}
.show {
opacity: 1;
transition: opacity 1000ms;
}
.hide {
opacity: 0;
transition: opacity 1000ms;
}
.end-page,
.end-page:hover {
opacity: 0.5;
pointer-events: none;
}
Optional fade in effect
If you don’t really need the fade-in effect for the appended posts, you may simply remove the following lines of codes from the JS file.
//initially add 'hide' class to the posts for fadein effect
posts[i].classList.add('hide')
/*********** fadein effect ***********/
//select all hidden posts
let hiddenPosts = postContainer.querySelectorAll('.hide'),
//set time out and make the posts visible
//css transition will fade in
timer = setTimeout( function(){
Array.prototype.map.call( hiddenPosts, function(el){
el.classList.remove('hide')
el.classList.add('show')
})
}, 300)
/*********** fadein effect ends ***********/
Similarly, remove the .show
& .hide
css codes from the style.css file. That will remove the fade-in effect. At this point, you can remove the default pagination code paginate_links()
from the related php files. Or you may use CSS to hide the element.
A task for you
Interested to play around the code? Here I have a task for you to think and do. Our JavaScript is event driven by load more button. Now, examine the window
and document
object and try to do an infinite scroll with this. Can you do that? Looking forward to that.
#task 2 – In this tutorial WordPress custom posts archive with REST API and ajax, we have used jQuery ajax. Try to convert it into pure JavaScript with fetch()
API.
Useful resources for JavaScript fetch API
Here you may find some useful resources for JavaScript tutorials –