Support » Developing with WordPress » Losing my mind – is it ever correct to use wp_reset_postdata?

  • Suppose you were writing a custom shortcode or block that includes a custom query. The following type of custom loop has always been a standard pattern:

    global $post;
    $results = get_posts(...);
    foreach ($results as $post) {
    	setup_postdata($post);
    	// do something
    }
    wp_reset_postdata();

    You’ll see that type of code throughout numerous tutorials (and existing plugins). I’ve used it numerous times myself.

    Have I come to a correct realization that that code should never be used?

    The reason being because this block may be processed outside the loop. The most common occasion is when using excerpts. For example, suppose your theme has a static homepage with another custom block (or perhaps it’s a widget in your sidebar) which loads the most recent blog post on the site and calls the_excerpt() followed by the_permalink().

    While calling the_excerpt(), it processes your custom block, and wp_reset_postdata() will reset the post variable incorrectly to the static homepage (original query) – so the_permalink() will print the homepage URL.

    It seems the only solution is to avoid using wp_reset_postdata entirely, instead using something like:

    $saved_post = $post;
    // now run your custom code
    $post = $saved_post;
    setup_postdata($post);

    I’ve been developing WordPress sites for a long time, and have only just stumbled over this edge case – it’s driving me a bit made that it seems like the standard code that WordPress typically recommends has been incorrect all this time, unless I’m missing something?

Viewing 12 replies - 1 through 12 (of 12 total)
  • The problem with your example is that it uses global $post; and uses that variable in the for loop. Your code should never set $post directly.
    If you use setup_postdata, you have to set $post, which you shouldn’t be doing.
    The wp_reset_postdata function, however, does affect $post, so they are not symmetrical. https://developer.wordpress.org/reference/functions/wp_reset_postdata/

    When using get_posts, you get an array of data. You don’t have to use the globals to process it, unless you need the various functions that use the current post by default. A lot of the functions take a post ID as a parameter.

    If you do use a function that sets the global variables, like the_post, then yes you should call wp_reset_postdata afterward. This is often the problem with pagination, caused by a poorly coded widget or shortcode.

    Thread Starter smerriman

    (@smerriman)

    Joy – that’s incorrect. When using setup_postdata, you have to set the global $post variable – that’s clearly outlined in the documentation:

    https://developer.wordpress.org/reference/functions/setup_postdata/

    It’s a standard pattern you’ll find throughout all WordPress tutorials.

    Yes, that’s what I said.

    If you use setup_postdata, you have to set $post, which you shouldn’t be doing.

    The key point is that you should not mess with $post directly, so you shouldn’t use setup_postdata either.
    There are years and years in which tutorials were written. Just because they exist, doesn’t mean they are correct. And the code evolved.

    Thread Starter smerriman

    (@smerriman)

    setup_postdata is the only possible method when you want to use template tags, which you will in the majority of cases. It works perfectly fine for all situations except the one I described.

    If you’re saying avoid setup_postdata and template tags, you seem to be answering a different question entirely.

    No, it’s not the only possibility. You can use the_post with a new WP_Query variable. https://developer.wordpress.org/reference/classes/wp_query/the_post/
    Your example code calls the reset after its for loop, which is correct, but your description of the problem indicates you are calling the reset inside the for loop, which makes no sense.
    If you change the globals, you definitely should call the reset afterwards, unless you are talking about the standard Loop in the theme code. And there is even a case to be made for doing that, if you need to go through the list of posts twice. You would reset to the beginning and output your second pass.

    Thread Starter smerriman

    (@smerriman)

    You still seem to be missing the entire point of this thread. WP_Query also uses the global $post variable, so in the context of my original question, it makes no difference; it results in exactly the same problem occurring.

    I am solely talking about the case where a block/shortcode, which correctly uses template tags and resets, ends up being called in someone else’s custom loop (usually as a result of the_excerpt).

    You can see a similar example of when this actually occurred in WordPress core in the Recent Posts widget:

    https://core.trac.wordpress.org/ticket/37312

    Their resolution was to avoid all uses of template tags as a quick fix. But the true solution seems to be to store and restore the $post variable manually, and never use wp_reset_postdata, since you can never guarantee your block won’t be loaded outside the loop.

    Edit – sorry, they didn’t avoid tags, but they used a very hacky foreach method to avoid calling the_post and reset entirely. Which seems a much worse solution.

    • This reply was modified 2 months, 1 week ago by smerriman.
    • This reply was modified 2 months, 1 week ago by smerriman.
    Thread Starter smerriman

    (@smerriman)

    OK, it appears I was correct – if you want to use template tags (whether via get_posts or WP_Query), you should never use the reset functions, despite that being their original use.

    Found a thread by one of the Gutenberg developers confirming this, where they recommended manually saving and restoring the $post variable as I suggested originally (or iterating over the $query->posts without using template tags).

    https://github.com/WordPress/gutenberg/issues/8035

    Moderator bcworkz

    (@bcworkz)

    What is your hesitancy to use wp_reset_postdata()? Your example is an example of poor practice: using a template tag outside the scope of the loop it was intended for.

    If you are going to utilize global values, you should restore such values when you’re finished to the way you found them. I don’t know why you’d use template tags outside the loop, but if you did, then you’re not finished with the related globals. After this questionable template tag use, you still should restore the global values afterwards.

    If you were absolutely sure the original global values would no longer be needed, maybe you could make a case for not restoring them. I think it would be very difficult to know with absolute surety. And it’s not like restoring is some onerous task. Just do it 🙂

    Thread Starter smerriman

    (@smerriman)

    What exactly is poor practice / questionable template tag use / usage outside the intended loop?

    The example I stated was none of this – my custom block starts a loop, uses a template tag, then resets the loop. Seemingly 100% normal practice and template usage, inside the intended loop.

    Let me give a concrete example of what I stated originally, with code, since you don’t seem to be understanding either. I’ll use WP_Query, since Joy seemed to think that would make a difference.

    Create a custom dynamic block or shortcode that runs the following code to display the most recent post title:

    $my_query = new WP_Query('posts_per_page=1');
    while ($my_query->have_posts()) : $my_query->the_post();
    the_title();
    endwhile;
    wp_reset_postdata();

    Do you see any questionable template tag usage outside the intended loop here?

    Now let’s say, completely unrelated to your code, a theme you’ve purchased uses their custom loop to display excerpts and links (I’ll exclude the actual HTML):

    $my_query = new WP_Query('posts_per_page=5');
    while ($my_query->have_posts()) : $my_query->the_post();
    the_excerpt();
    the_permalink();
    endwhile;
    wp_reset_postdata();

    This also appears like normal loops and template tag usage.

    But if any of those 5 posts it loads includes your block anywhere in the content, it breaks, assuming you haven’t set manual excerpts.

    The call to the_excerpt parses my block. At end of my block’s call, wp_reset_postdata() restores the $post variable to the *original* one, outside the theme’s custom loop. the_permalink() will then completely unexpectedly have the wrong output.

    The only way to avoid this, besides avoiding template tags, is to rewrite my block code to:

    $old_post = $post;
    $my_query = new WP_Query('posts_per_page=1');
    while ($my_query->have_posts()) : $my_query->the_post();
    the_title();
    endwhile;
    $post = $old_post;
    setup_postdata($post);

    Since you can never guarantee when your block will be used by themes / other developers, the original version with resetting the query the ‘intended’ way is therefore incorrect.

    I can see that your question is about recursion. You are doing something inside the content that should only be done by the theme, which is to use the globals in a loop. The way that WP functions work expects only a standard Loop with no nested loops.
    So if you are writing a loop that goes in a block, don’t use standard template functions (that use globals).

    Thread Starter smerriman

    (@smerriman)

    OK, so you’ve come to the same conclusion as I did – it is never correct to use wp_reset_postdata inside a custom block/shortcode (nor indeed, a new WP_Query, like your earlier suggestion). Even though there is no recursion involved from your point of view, there is no guarantee it won’t happen.

    Will be interested in hearing from others like bcworkz who was suggesting it was OK though, as I see it in widespread usage.

    • This reply was modified 2 months, 1 week ago by smerriman.
    Moderator bcworkz

    (@bcworkz)

    I misunderstood your example, apologies. Still, if you mess with globals, put them back the way you found them. Maybe wp_reset_postdata() isn’t always the way to do it, but something still needs to be done. Don’t think in terms of “Always use wp_reset_postdata().” Think “What must I do to not screw up other code.”

    In the case of block queries, I’m with Joy, don’t use template tags (you’re not in the template context anyway) and don’t mess with globals. End of problem.

Viewing 12 replies - 1 through 12 (of 12 total)
  • You must be logged in to reply to this topic.