I am in the process of rewriting a lot of my code and restructuring a lot of my theme files to use template parts (i.e. using get_template_part()). I’ve come upon a situation where I want to use the same template parts whether I’m using the main query or a secondary custom query (using WP_Query). The template parts also use core functions that rely on the main query (such as conditionals).

I can get around the problem by overwriting the main query (or overwriting the $wp_query global which holds the main query, the main query still exists) with my custom query and resetting the main query once I’m done. This also means I can use the same loop for the main query and my custom queries. For example:

// Query
if ( $i_need_a_custom_query ) {
    $wp_query = new WP_Query($custom_query_args);
}

// The Loop
if ( have_posts() ) : while ( have_posts() ) : the_post();

    // Do some loop stuff
    // and call some functions that rely on the main query

// End the loop
endwhile; endif;

// I'm done so reset the query
wp_reset_query();

This works. No problem at all, but it seems a bit of a hack to me. So my question is:

  • Am I right to be weary of overwriting the main query like this?
  • Are there any side effects I’m missing?
  • And am I correct in assuming that calling wp_reset_query() isn’t costly (i.e. it isn’t actually re-running the query but simply resetting the globals which are still around somewhere)?

Edit to clarify:

I am only using the custom queries in question as secondary queries, that’s the whole point of the question. I want to use the same template wether I am using a custom secondary query or using the main query. I understand that what I am doing is essentially the same as using query_posts(), which is a bad idea. As far as I’m aware and broadly speaking, the 2 drawbacks to using query_posts() are 1. Performance, which isn’t an issue because I am only doing this when running a secondary custom query anyway and 2. Unintended consequences from changing the global $wp_query, which is exactly what I do want to happen (and as I said is actually working perfectly).

3 Answers
3

In general, I agree with Howdy_McGee that one should avoid overwriting the main query unless absolutely necessary – more often than not, modifying the main query with something like a 'pre_get_posts' hook is a better solution to such scenarios.

Manually overwriting the global $wp_query can cause all sorts of unintended behaviors if you are not exceedingly careful, including breaking pagination, among other things. It is, in essence, an even more simplistic equivalent to the often criticized query_posts() function, which the WordPress Code Reference notes:

This function will completely override the main query and isn’t intended for use by plugins or themes. Its overly-simplistic approach to modifying the main query can be problematic and should be avoided wherever possible. In most cases, there are better, more performant options for modifying the main query such as via the ‘pre_get_posts’ action within WP_Query.

In this instance however, where you require either:

  • Simultaneous access to both the main query as well as a secondary query for their distinct content and/or conditional tags
  • A generic implementation in something like a reusable template part that can be applied to either the main query or any custom query

then one solution is to store the “contextual” WP_Query instance in a local variable, and invoke WP_Query‘s conditional methods on that variable instead of using their globally-available conditional tag function counterparts (which always explicitly reference the main query, global $wp_query).

For instance, if you wanted your “contextual query variable” to refer to a custom secondary query in single and archive templates for your custom post type, my-custom-post-type, but refer to the main query in every other case, you could do the following:

Theme’s functions.php file, or a plugin file:

function wpse_232115_get_contextual_query() {
  global $wp_query;
  static $contextual_query;

  // A convenient means to check numerous conditionals without a bunch of '||' operations,
  // i.e "if( $i_need_a_custom_query )"
  if( in_array( true,
    [
      is_singular( 'my-custom-post-type' ),
      is_post_type_archive( 'my-custom-post-type' )
    ]
  ) ) {
    // Create the contextual query instance, if it doesn't yet exist
    if( ! isset( $contextual_query ) ) {
      $query_args = [
        //...
      ];

      $contextual_query = new WP_Query( $query_args );
    }

    return $contextual_query;
  }

  return $wp_query;
}

“Generic” template-part files:

$query = wpse_232115_get_contextual_query();

// Contextual Query loop (loops through your custom query when 'my-custom-post-type's
// are being displayed, the main $wp_query otherwise.
if ( $query->have_posts() ) : while ( $query->have_posts() ) : $query->the_post();
  // Tags dependent on The Loop will refer to the contextual query if The Loop
  // was set up with the "$query->" prefix, as above. Without it, they always
  // refer to the main query.
  the_title();

  // The global conditional tags always refer to the main query
  if( is_singular() ) {
    //... Do stuff is the main query result is_singular();
  }

  // Conditional methods on the $query object reference describe either
  // the main query, or a custom query depending on the context.
  if( $query->is_archive() ) {
    //... Do stuff if the $query query result is an archive
  }
// End the loop
endwhile; endif;

I’m not sure if this is the best solution, but it’s how I would think to approach the problem.

Leave a Reply

Your email address will not be published. Required fields are marked *