WordPress – Get Posts with Category data

One major issue that I see in your code is the nested foreach loop. If a post has more than one category then the post would be rendered again and again for each category.

Another thing is that using wp_get_post_categories() might cause performance issues as the result of the function isn’t cached and the database is called every time the function is called.

To replace the hardcoded conditional logic you could consider adding controls to the block with InspectorControls so that the user can choose for example, if the category or categories should be displayed.

Here’s an example how I think your code could be improved. The code below is untested and should be checked and modified to fit your exact needs and context.

function my_render_callback($block_attributes, $content) {
  // use plural for variable name to better reflect what it contains
  // could also use WP_Query here directly
  $posts = get_posts([
    'posts_per_page' => 5, // this could also come from $block_attributes
  ]);
  
  // map (i.e. loop posts and apply the callback to each of them) found posts to html
  // pass block attributes to renderer for conditional logic etc.
  $posts_html = array_map(
    'my_post_renderer',
    $posts,
    $block_attributes
  );

  // return html as a string
  return sprintf(
    '<div class="some-wrapper">
      <div class="another-wrapper-maybe">
        %s
      </div>
    </div>',
    implode('', $posts_html)
  );
}

function my_post_renderer(WP_Post $post, array $block_attributes) {
  // control the html output with block attributes set with InspectorControls
  $show_only_first_category = ! empty( $block_attributes['onlyDisplayFirstCategory'] );
  $categories_to_display = $block_attributes['categoriesToDisplay']; // term name/term_id/slug, or an array of them

  // helper variable for conditional logic
  $display_categories = $categories_to_display && has_term( $categories_to_display, 'category', $post );
  
  // format post to html
  // sprintf makes the resulting html a little easier to read than bunch of echos
  // sprintf also eliminates the need to use output buffering
  return sprintf(
    '<div>
      <div class="blog-image">%s</div>
      <div class="blog-data">
        <div class="data-top">
          <div class="blog-type">
            %s
          </div>
          <h3 class="blog-title">%s</div>
        </div>
        <div class="data-bottom">
          <div class="blog-info">
            <p class="blog-date">%s</p>
            <div class="blog-link">
              <a href="%s">%s</a>
            </div>
          </div>
        </div>
      </div>
    </div>',
    // get html img tag with srcset, lazy load, and other good stuff WP provides
    // change size, if needed
    get_the_post_thumbnail( $post, 'medium' ),
    // lets use our own helper function to format the category html
    $display_categories ? my_post_category_renderer( $post, $show_only_first_category ) : '',
    // get title directly from the post object or use get_the_title() for default processing and filtering to be applied
    esc_html( get_the_title( $post ) ),
    // use function to let WP handle date formatting based on the format set in the site settings 
    esc_html( get_the_date( '', $post ) ),
    // use functions instead of guid so that url will use the permalink structure set in the site settings
    esc_url( get_permalink( $post ) ), 
    // make anchor text translatable
    esc_html__( 'Read more', 'your-text-domain' )
  );
}

function my_post_category_renderer(WP_Post $post, bool $only_first_category) {
  // prefer get_the_category() over wp_get_post_categories() as the result of the second one isn't cached
  // get_the_category() returns an array of WP_Terms
  $post_categories = get_the_category($post->ID); 
  // demonstration of how to display only the first or all of the categories attached to the post
  if ( $only_first_category ) {
    return my_post_category_html( $post_categories[0] );
  } else {
    return implode(
      '',
      array_map('my_post_category_html', $post_categories)
    );
  }
}

function my_post_category_html(WP_Term $category) {
  return sprintf(
    '<span class="%s">%s</span>',
    esc_attr( $category->slug ),
    esc_html( $category->name )
  );
}