Create Infinite Scroll In WordPress With AJAX Request — Custom Code Without A Plugin.

  1. Create a nonce and pass it to the JavaScript file, so it can be verified.
  2. We will create a function that renders the initial set of posts.
  3. Create a function that verifies the nonce and then makes a WP_Query to get more posts
  4. We use Intersection Observer API to track the load more button, so that when it comes into view, we trigger a load more AJAX request through JavaScript function, that calls the above PHP function to make a query and loop through posts and displays them.
namespace MyApp;function asset_loader() {
// Registers scripts.
wp_register_script( 'app', 'url-path-to/loadmore.js' ), [ 'jquery' ], filemtime( get_stylesheet_directory() . '/file-path-to/loadmore.js' ), true );


wp_enqueue_script( 'app' );

wp_localize_script( 'app', 'siteConfig', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'ajax_nonce' => wp_create_nonce( 'loadmore_post_nonce' ),
] );
}

add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\\asset_loader' );
<?php
/**
* Post Card
*
* Note: Should be called with The Loop
*/
namespace MyApp;

$post_permalink = get_the_permalink();
?>

<section id="post-<?php the_ID(); ?>"
class="mb-5 lg:mb-8 xl:mb-10 px-1 w-full overflow-hidden sm:w-1/2 md:w-1/3 lg:w-1/4">
<header>
<a href="<?php echo esc_url( $post_permalink ); ?>" class="block">
<figure class="img-container relative w-full">
<?php the_post_thumbnail( 'post-thumbnail', [ 'class' => 'absolute w-full h-full left-0 top-0 object-cover' ] ); ?>
</figure>
</a>
</header>
<div class="post-content">
<p class="line-clamp-5 leading-6"><?php echo wp_strip_all_tags( get_the_content() ); ?></p>
</div>
</section>
<?php
/**
* Loadmore functions
*
*/
namespace MyApp;
use \WP_Query;

/**
* Load more script call back
*
*
@param bool $initial_request Initial Request( non-ajax request to load initial post ).
*
*/
function ajax_script_post_load_more( bool $initial_request = false ) {

if ( !$initial_request && ! check_ajax_referer( 'loadmore_post_nonce', 'ajax_nonce', false ) ) {
wp_send_json_error( __( 'Invalid security token sent.', 'text-domain' ) );
wp_die( '0', 400 );
}

// Check if it's an ajax call.
$is_ajax_request = ! empty( $_SERVER['HTTP_X_REQUESTED_WITH'] ) &&
strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) === 'xmlhttprequest';
/**
* Page number.
* If get_query_var( 'paged' ) is 2 or more, its a number pagination query.
* If $_POST['page'] has a value which means its a loadmore request, which will take precedence.
*/
$page_no = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;
$page_no = ! empty( $_POST['page'] ) ? filter_var( $_POST['page'], FILTER_VALIDATE_INT ) + 1 : $page_no;

// Default Argument.
$args = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 4, // Number of posts per page - default
'paged' => $page_no,
];

$query = new WP_Query( $args );;

if ( $query->have_posts() ):
// Loop Posts.
while ( $query->have_posts() ): $query->the_post();
get_template_part( 'template-parts/post-card' );
endwhile;

// Pagination for Google.
if ( ! $is_ajax_request ) :
$total_pages = $query->max_num_pages;
get_template_part( 'template-parts/common/pagination', null, [
'total_pages' => $total_pages,
'current_page' => $page_no,
] );
endif;
else:
// Return response as zero, when no post found.
wp_die( '0' );
endif;

wp_reset_postdata();

/**
* Check if its an ajax call, and not initial request
*
*
@see https://wordpress.stackexchange.com/questions/116759/why-does-wordpress-add-0-zero-to-an-ajax-response
*/
if ( $is_ajax_request && ! $initial_request ) {
wp_die();
}

}

/*
* Load more script ajax hooks
*/
add_action( 'wp_ajax_nopriv_load_more', __NAMESPACE__ . '\\ajax_script_post_load_more' );
add_action( 'wp_ajax_load_more', __NAMESPACE__ . '\\ajax_script_post_load_more' );

/*
* Initial posts display.
*/
function post_script_load_more() {

// Initial Post Load.
?>
<div class="vl-container mt-20 md:mt-28 xl:mt-32 mb-28 md:mb-36 xl:mb-40">
<div id="load-more-content" class="flex flex-wrap -mx-1 overflow-hidden">
<?php
ajax_script_post_load_more( true );

// If user is not in editor and on page one, show the load more.
?>
</div>
<button id="load-more" data-page="1"
class="load-more-btn mt-20 block mx-auto px-4 py-2 border border-transparent transition ease-in-out duration-150 cursor-not-allowed">
<span class="screen-reader-text"><?php esc_html_e( 'Load More', 'text-domain' ); ?></span>
<svg class="animate-spin -ml-1 mr-3 h-8 w-8 text-brand-light-blue" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</button>
</div>
<?php
}

/**
* Create a short code.
*
* Usage echo do_shortcode('[post_listings]');
*/
add_shortcode( 'post_listings', __NAMESPACE__ . '\\post_script_load_more' );
<?php
/**
* Pagination Template.
*
* To be used inside the WordPress loop.
* Pagination for Google.
*
*
@package Aquila
*/

if ( empty( $args['total_pages'] ) || empty( $args['current_page'] ) ) {
return null;
}

if ( 1 < $args['total_pages'] ) {
?>
<div id="post-pagination" class="hidden-pagination hidden" data-max-pages="<?php echo esc_attr( $args['total_pages'] ); ?>">
<?php
echo paginate_links( [
'base' => get_pagenum_link( 1 ) . '%_%',
'format' => 'page/%#%',
'current' => $args['current_page'],
'total' => $args['total_pages'],
'prev_text' => __( '« Prev', 'aquila' ),
'next_text' => __( 'Next »', 'aquila' ),
] );
?>
</div>
<?php
}
echo do_shortcode('[post_listings]');
( function( $ ) {
class LoadMore {
constructor() {
this.ajaxUrl = siteConfig?.ajaxUrl ?? '';
this.ajaxNonce = siteConfig?.ajax_nonce ?? '';
this.loadMoreBtn = $( '#load-more' );

this.options = {
root: null,
rootMargin: '0px',
threshold: 1.0, // 1.0 means set isIntersecting to true when element comes in 100% view.
};

this.init();

}

init() {

if ( ! this.loadMoreBtn.length ) {
return;
}
this.totalPagesCount = $( '#post-pagination' ).data( 'max-pages' );
/**
* Add the IntersectionObserver api, and listen to the load more intersection status.
* so that intersectionObserverCallback gets called if the element intersection status changes.
*
*
@type {IntersectionObserver}
*/
let observer = new IntersectionObserver( ( entries ) => this.intersectionObserverCallback( entries ), this.options );
observer.observe( this.loadMoreBtn[0] );
}

/**
* Gets called on initial render with status 'isIntersecting' as false and then
* everytime element intersection status changes.
*
*
@param {array} entries No of elements under observation.
*
*
@return null
*/
intersectionObserverCallback( entries ) { // array of observing elements

// The logic is apply for each entry ( in this case it's just one loadmore button )
entries.forEach( entry => {
// If load more button in view.
if ( entry?.isIntersecting ) {
this.handleLoadMorePosts();
}
} );
}

/**
* Load more posts.
*
* 1.Make an ajax request, by incrementing the page no. by one on each request.
* 2.Append new/more posts to the existing content.
* 3.If the response is 0 ( which means no more posts available ), remove the load-more button from DOM.
* Once the load-more button gets removed, the IntersectionObserverAPI callback will not be triggered, which means
* there will be no further ajax request since there won't be any more posts available.
*
*
@return null
*/
handleLoadMorePosts() {

// Get page no from data attribute of load-more button.
const page = this.loadMoreBtn.data( 'page' );
if ( !page ) {
return null;
}

const nextPage = parseInt(page) + 1; // Increment page count by one.

$.ajax( {
url: this.ajaxUrl,
type: 'post',
data: {
page: page,
action: 'load_more',
ajax_nonce: this.ajaxNonce
},
success: ( response ) => {

this.loadMoreBtn.data( 'page', nextPage );
$( '#load-more-content' ).append( response );
this.removeLoadMoreIfOnLastPage(nextPage)
},
error: ( response ) => {
console.log( response );
},
} );
}
/**
* Remove Load more Button If on last page.
*
*
@param {int} nextPage New Page.
*/
removeLoadMoreIfOnLastPage = ( nextPage ) => {
if ( nextPage + 1 > this.totalPagesCount ) {
this.loadMoreBtn.remove();
}
}
}

new LoadMore();

} )( jQuery );

--

--

--

👤 Full Stack Developer at rtCamp, Speaker, Blogger, YouTuber, Wordpress, React, Node, Laravel Developer http://youtube.com/ImranSayedDev

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

LeetCode

If You Use Vue.JS, You Should Use This Too

How To Integrate Cloud Services for Image Upload in a Node.js + React Web App

Drawing of a man on his laptop

Functional Memoization in Javascript

The Quest to the Perfect Full-Stack Framework Begins

React: Understanding State

Computer

Leetcode 352: Data Stream as Disjoint Intervals

How to Build a Chat App using ReactNative and Firebase (LinkedIn Clone)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Imran Sayed

Imran Sayed

👤 Full Stack Developer at rtCamp, Speaker, Blogger, YouTuber, Wordpress, React, Node, Laravel Developer http://youtube.com/ImranSayedDev

More from Medium

Progressive Web App(PWA)

Vue— What is it? — Ch 01

15 Best JavaScript frameworks and libraries you must learn in 2022!

15 Best JavaScript frameworks and libraries you must learn in 2022!

Chrome’s Developer Tools — A Must Have

screenshot of Chrome Developer Tools