Skip to content

JavaScript fetch API to load WordPress posts

javascript fetch api

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 –

  1. Make sure you have pagination by checking total posts and posts per page from reading settings.
  2. If there is pagination available, you can see the page links just above the load more button.
pagination image reference
example pagination image

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 keywordAsync 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 –

JS Callbacks, Promises, Async Await

Leave a Reply