1

I'm querying posts using the following wp query:

function custom_retrieve_posts_function() {

$paged = get_query_var( "paged" ) ? get_query_var( "paged" ) : 1;
$args = array(
        'post_type' => 'my_post_type',
        'posts_per_page' => 7,
        'paged' => $paged
);
$query = new \WP_Query( $args );
$output = "";
if ( $query->have_posts() ) {

  while ( $query->have_posts() ) {

    // Iterate next post of query result
    $query->the_post();

    $output .= get_the_content();

  }

  // Only display arrows if links have been obtained, so do it in this way
  $previous_link = get_previous_posts_link(
    esc_html__( 'Zurück', 'custom-text-domain' )
  );
  
  $next_link = get_next_posts_link(
    esc_html__( 'Weiter', 'custom-text-domain' ),
    $query->max_num_pages
  );

  $nav_links = "<div id=\"navigation-links\">".

    "<span id=\"previous-link\">".
    ( $previous_link ? "&#8678 {$previous_link}" : "" ).
    "</span>".

    "<span id=\"next-link\">".
    ( $next_link ? "{$next_link} &#8680" : "" ).
    "</span>".

  "</div>";

  $output .= $nav_links;

  // Reset the wp_query loop
  wp_reset_postdata();
      
 } else {

   $output = "<p class=\"no-results\">Oops!</p>";

}

echo $output;

}

Problem is that certain posts of page n are seemingly randomly repeated on page(s) m, with m > n.

The amount / limit of posts to be shown in feeds and blogs is 7, I've set that in the wp admin options. Still, same problem persists.

Any idea why this is happening?

UPDATE

The function custom_retrieve_posts_function() (defined in a namespace within a class etc., but all of this is omitted here for simplicity) is called within a PHP script bound to the AJAX hook responsible for executing a custom filtered query via ajax.

So the script is called like this:

Main Plugin File holds this:

add_action(
  'wp_ajax_myplugin_search_custom',
  function() {
    require MYPLUGIN_AJAX_DIR.'/custom_search.php';
    wp_die();
  }
);

add_action(
  'wp_ajax_nopriv_myplugin_search_custom',
  function() {
    require MYPLUGIN_AJAX_DIR.'/custom_search.php';
    wp_die();
  }
);

With the content of AJAX_DIR.'/custom_search.php' being (again simplified, all naming conflicts are 100% avoided, so all classnames / namespaces are again omitted):

check_ajax_referer( 'ajax-nonce-content' );
require MYPLUGIN.'/custom_search_function.php';
custom_retrieve_posts_function();

I then also have a page template defined in my themes/mycustomtheme/templates directory, and within that page template, I simply call

require MYPLUGIN.'/custom_search_function.php';
custom_retrieve_posts_function();

Note that all of this works perfectly, expect from the problem that already displayed posts are re-displayed on subsequently paginated pages.

UPDATE 2

Sadly, I'm not able to make it working after making the commented adaptations via js (additionally pass the number of the page of the pagination to query). Like when I do this query via AJAX:

$args = array(
        'post_type' => 'my_post_type',
        'tax_query' => $tax_query_array,
        'orderby' => 'meta_value_num',
        'meta_key' => 'my_metas_key',
        'order' => 'ASC',
        'posts_per_page' => 7,
        'paged' => 2
);

I still get one of the posts already shown on page 1 (a random one).

Same if I add $args['offset'] = 7 and / or $args['page'] = 2, so I'm confused; what am I still not properly understanding here??

6
  • 1
    is there a reason you're using a secondary custom query instead of modifying the main query with pre_get_posts to change its parameters? Pagination functions in WordPress all work off of the main query by default. Also if the main query has 4 pages but your query has 5, then the 5th page may not work as you expected because there is not 5th page on the main query. You might find it 404's instead. Also what file is this in? Is it a page template? Are you trying to build a CPT archive? What is it trying to implement?
    – Tom J Nowell
    Jan 7, 2022 at 18:47
  • This loop is called in a callback which is called from the frontend via AJAX (correctly setup wp-ajax) request to query for specific posts using values provided via the frontend. Does that help you? I'm unaware about the fact that WP has different kinds of queries (main and not-main, seemingly), but thought, the docs say you should use the WP_Query class for page templates, so I'll most likely also use it in ajax calls..?
    – DevelJoe
    Jan 7, 2022 at 18:57
  • To be precise, I use this callback both on a page template (I simply call the callback from within the page template) and an ajax request, fired from the frontend via that same page to do the above-mentioned.
    – DevelJoe
    Jan 7, 2022 at 19:13
  • Ah! So there is no main query because AJAX. The main query is the query that was created using the parameters generated by the URL + rewrite rules, and is what functions such as the_post or have_posts etc refer to, much like there's a current post. If your query is inside an AJAX callback that's super important information, AJAX requests do not have pagination, you have to provide it. It looks like you have not provided the full code, you'll need to edit your question to include this. Don't try to be helpful and hide things you think aren't relevant, that's probably were the error was
    – Tom J Nowell
    Jan 7, 2022 at 19:21
  • Also note that the first paragraph of the doc you linked to is mainly talking about how it breaks pagination, and how it is not the best way to modify the default query.
    – Tom J Nowell
    Jan 7, 2022 at 19:22

2 Answers 2

3

Because of this line:

$paged = get_query_var( "paged" ) ? get_query_var( "paged" ) : 1;

This is inside a function that is used on both an AJAX handler, and on a page request. get_query_var pulls the parameters from the main query ( aka the primary WP_Query that powers functions such as the_post, have_posts() etc ). But this will never work in an AJAX request.

For this reason, when you request the next page from AJAX it will always result in page 1. If you want something other than page 1 then you need to include the number of the page you want in the javascript that sends the AJAX request, pass it along, then read it and put it in the WP_Query parameters directly. Even if there was a main query it would not be the same main query.

Remember, every request to the server loads WordPress from a blank slate, AJAX request handlers share nothing with the page that your browser has open unless you explicitly send them the data you want to share along with the request as POST/GET variables. There is no shared pagination state behind the scenes or magic that glues it together.

Additionally, because you included the pagination HTML in the AJAX response you'll now have a next page link that leads to page 2, but triggers AJAX that loads page 1 ( why would it know to load page 2? get_query_var won't work, there is no main query to get a query var from! ).


What's more, I have a strong suspicion that you're either unaware of archive-mycustompost.php, the pre_get_posts filter, or the ability to change the URL of the CPT archive when registering the CPT, or that query parameters for the main query come from the URL and you can add extra parameters e.g. example.com/?s=test&posts_per_page=7&paged=2 gets the second page of a search query for "Test" that has 7 posts per page. In the majority of cases a secondary query and a custom page template are completely unnecessary.

39
  • Well the thing is that sometimes it actually works, and only the subsequent posts are shown on the subsequently paginated pages. But this seems to be completely independent of cache / sessions whatsoever, which is why I figured there must be sth I'm doing wrong. So you're telling me that there's no way to do what I want / that I need to fire an AJAX request for every single pagination?
    – DevelJoe
    Jan 7, 2022 at 19:35
  • And, if I'm not wrong, the "paged" query var should be retrieved from the links generated via the get_next_posts_links() and the get_previous_posts_links() called within my callback; because when I click on them, it indeed adds the page/2/ etc. slugs onto the URL and reloads the page. Maybe I should follow the approach of the paged parameter for static front pages, I'll give it a try, one sec (what I mean is this).
    – DevelJoe
    Jan 7, 2022 at 19:43
  • I suspect there is some confusion here and inconsistency in the information you have given. This answer is written under the assumption that the code your provided is inside PHP executed in a WP AJAX handler, and only in a WP AJAX handler, is this correct? Remember, get_previous_posts_links etc all refer to the main query, but AJAX request handlers do not have a main query. Also your last comment contradicts the comment about this being AJAX. Can you clarify unambiguously?
    – Tom J Nowell
    Jan 7, 2022 at 19:48
  • I see your edit updates, I will adjust my answer but you will not like the result
    – Tom J Nowell
    Jan 7, 2022 at 19:51
  • 1
    @DevelJoe literally anywhere that refers to the main loop is referring to the main query, literally any query that was created by using new WP_Query(), query_posts or get_posts or one of the other functions is not the main query. It's one of the most foundational fundamental beginner level things you should have learnt, so basic that it may never have been directly mentioned because custom queries were introduced instead as custom queries.
    – Tom J Nowell
    Jan 7, 2022 at 20:03
0

Okay finally got this working. For reasons I will not detail here, I preferred a pagination solution which uses ONE single callback which I use everywhere where I need the pagination. In other words, I call the callback call on both upon page load of the template pages needing the query AND as well when click on a pagination link OR applying any search filter to query for the posts.

In cases which do not require you to do this, of course you can refer to the discussion among @Tom J Nowell and me (in his answer above, thx again!). If possible with your design, you thus may use the WP REST API (instead of using the AP AJAX API) to create the request, an archive page + WP's main query loop + the related pagination features associated to that main query.

If you prefer to create a WP pagination solution which uses exclusively WP AJAX via $_POST, you can easily do this by providing the additional number of the page you're requesting to your ajax handler via JS, and simply use 1 if none is provided. So my function above became:

function custom_retrieve_posts_function( $paged = 1 ) {

if ( isset($_POST['page_to_request'] ) ) {
  $paged = intval( sanitize_text_field( isset( $_POST['page_to_request'] ) ) );
}

$args = array(
  'post_type' => 'my_custom_post_type',
  'tax_query' => $tax_query_array,
  'orderby' => array(
    'meta_value_num' => 'ASC',
    'date' => 'DESC'
   ),
   'meta_key' => 'my_custom_meta_key',
   'posts_per_page' => 7,
   'paged' => $paged
);

$query = new \WP_Query( $args );
$output = "";
if ( $query->have_posts() ) {

  while ( $query->have_posts() ) {

    // Iterate next post of query result
    $query->the_post();

    $output .= get_the_content();

  }

  // Only display arrows if links have been obtained, so do it in this way
  $max_num_pages = $query->max_num_pages;

  $nav_links = "<div id=\"navigation-links\">".

    // If we're not on the first page, add a link allowing to go back to
    // the one previous to the one currently requested
    "<span id=\"previous-link\"".(
      ( $max_num_pages !== 1 ) ?
      " data-previous-page='".( $paged - 1 )."'>&#8678 Back" :
      ">"
    ).
    "</span>".

    // If we're not on the last page, add a link allowing to go forward to
    // the page subsequent to the one currently requested 
    "<span id=\"next-link\"".(
      ( $max_num_pages > $paged ) ?
      " data-next-page='".( $paged + 1 )."'>Next &#8680" :
      ">"
    ).
    "</span>".

  "</div>";

  $output .= $nav_links;

  // Reset the wp_query loop
  wp_reset_postdata();
      
 } else {

   $output = "<p class=\"no-results\">Oops!</p>";

}

echo $output;

}

Note that I've added date into my order clause, additionally to what's been specified in my question above. This is because I've found out that page n of your pagination may show posts of previous pages m where m < n, if you have many posts which have the same meta value. Even using the offset parameter of the wp query etc. will not eliminate this problem. So I thought of first using the ordering I need in my case (the meta key), and then subsequently according to another value which is unique to posts, such as the posts' date. My thought was that like this I can assure that the posts will always be retrieved in exactly the same order, even if their meta value is the same. And well, to have the same order is crucial for proper pagination, no matter the context in which the function is called.

As I imagine, WP transforms this WP_Query array into a SQL query to query for the according data. So, when using a data field which is unique to a post, but not a date but sth like a primary key in the DB, this may result in faster query times. Hence, instead of the date order key, you may use ID.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.