Add “load more” functionality to an AJAX response

This is all the code to also add “load more” functionality:

/*
  Widget name  : Filtering (accommodation)
  Parameters  : 'cpt'
  Function   : filtering()
*/
function filtering($filtering){

  $taxon = get_object_taxonomies($filtering['cpt']);
  $terms = get_terms($taxon[0]);

  $accs = new WP_Query([
    'post_type' => $filtering['cpt'],
    'posts_per_page' => 6,
    'order_by' => 'date',
    'order' => 'desc',
    'tax_query' => array(
      array(
        'taxonomy' => $taxon[0],
        'field' => 'slug',
        'operator' => 'EXISTS'
      )
    )
  ]);
  $found = $accs->found_posts;
  ?>

  <div class="filtering x-9">
    <div class="filtering-cpt">
      <a class="filtering-cpt-sin active" href="#!" data-load="<?php echo $found; ?>" data-type="<?php echo $filtering['cpt']; ?>" data-slug=""><?php _e('All','xlate'); ?></a>
      <?php foreach($terms as $term) : ?>
      <a class="filtering-cpt-sin" href="#!" data-load="<?php echo $term->count; ?>" data-type="<?php echo $filtering['cpt']; ?>" data-slug="<?php echo $term->slug; ?>"><?php echo $term->name; ?></a>
      <?php endforeach; ?>
    </div>

    <?php

    if ($accs->have_posts()): ?>
      <div class="filtering-res x-9">
        <?php while($accs->have_posts()) : $accs->the_post(); ?>
        <div class="filtering-res-sin x-3">
          <div class="filtering-res-sin-top">
            <?php
            $image_data = wp_get_attachment_image_src( get_post_thumbnail_id( get_the_ID() ), "large" );
            $get_map_link = get_post_custom_values($key = 'map');
            ?>
            <img src="<?php echo $image_data[0]; ?>" width="<?php echo $image_data[1]; ?>" height="<?php echo $image_data[2]; ?>" alt="<?php echo get_the_title(); ?>" />
          </div>
          <div class="filtering-res-sin-bot darkbg cropit xx-anim">
            <div class="filtering-res-sin-bot-tit"><?php echo get_the_title(); ?></div>
            <div class="filtering-res-sin-bot-bar"></div>
            <div class="filtering-res-sin-bot-tog">Contact information <img src="https://lgkm.aero/wp-content/uploads/2022/11/load-more-plus-white-14x14-1-1.svg" width="12" height="12" alt="+" /></div>
            <div class="filtering-res-sin-bot-con"><?php echo get_the_content(); ?></div>
            <a class="filtering-res-sin-bot-but" href="<?php echo (($get_map_link != '') ? $get_map_link[0] : ''); ?>"><img src="https://lgkm.aero/wp-content/uploads/2022/11/find-on-map-white-26x34-1.svg" width="26" height="34" alt="|" /> Find on map</a>
          </div>
        </div>
        <?php endwhile; ?>
      </div>
      <div class="filtering-load x-12" data-load="<?php echo $found; ?>" data-type="<?php echo $filtering['cpt']; ?>" data-slug="">Load more <img src="https://lgkm.aero/wp-content/uploads/2022/11/shop-plus-20x20-1.svg" width="14" height="14" alt="+" /></div>
      <?php wp_reset_postdata();
    endif; ?>
  </div>

<?php }

function filtering_ajax() {

  $catCpt = $_POST['type'];
  $terms = get_object_taxonomies($catCpt);
  $catSlug = $_POST['category'];

  if ($catSlug){
    $ajaxposts = new WP_Query([
      'post_type' => $catCpt,
      'posts_per_page' => 3,
      'orderby' => 'date',
      'order' => 'desc',
      'tax_query' => array(
        array(
          'taxonomy' => $terms[0],
          'field' => 'slug',
          'terms' => $catSlug,
        )
      )
    ]);
  } else {
    $ajaxposts = new WP_Query([
      'post_type' => $catCpt,
      'posts_per_page' => 3,
      'orderby' => 'date',
      'order' => 'desc',
      'tax_query' => array(
        array(
          'taxonomy' => $terms[0],
          'field' => 'slug',
          'operator' => 'EXISTS'
        )
      )
    ]);
  }

  $found = $ajaxposts->found_posts;
  $response="";

  if($ajaxposts->have_posts()) {
    while($ajaxposts->have_posts()) : $ajaxposts->the_post();
      $image_data = wp_get_attachment_image_src( get_post_thumbnail_id( get_the_ID() ), "large" );
      $get_map_link = get_post_custom_values($key = 'map');
      $response .= '
      <div class="filtering-res-sin x-3">
        <div class="filtering-res-sin-top">
          <img src="' . $image_data[0] . '" width="' . $image_data[1] . '" height="' . $image_data[2] . '" alt="' . get_the_title() . '" />
        </div>
        <div class="filtering-res-sin-bot darkbg cropit vsb xx-anim">
          <div class="filtering-res-sin-bot-tit">' . get_the_title() . '</div>
          <div class="filtering-res-sin-bot-bar"></div>
          <div class="filtering-res-sin-bot-tog">Contact information <img src="https://lgkm.aero/wp-content/uploads/2022/11/load-more-plus-white-14x14-1-1.svg" width="12" height="12" alt="+" /></div>
          <div class="filtering-res-sin-bot-con">' . get_the_content() . '</div>
          <a class="filtering-res-sin-bot-but" href="' . (($get_map_link != '') ? $get_map_link[0] : '') . '"><img src="https://lgkm.aero/wp-content/uploads/2022/11/find-on-map-white-26x34-1.svg" width="26" height="34" alt="|" /> Find on map</a>
        </div>
      </div>';
    endwhile;
  } else {
    $response="";
  }

  echo $response;
  exit;
}
add_action('wp_ajax_filtering_ajax', 'filtering_ajax');
add_action('wp_ajax_nopriv_filtering_ajax', 'filtering_ajax');



function filtering_ajax_load() {

  $catCpt = $_POST['type'];
  $terms = get_object_taxonomies($catCpt);
  $catOff = $_POST['offset'];
  $catSlug = $_POST['category'];

  if ($catSlug){
    $ajaxposts = new WP_Query([
      'post_type' => $catCpt,
      'posts_per_page' => 3,
      'offset' => $catOff,
      'orderby' => 'date',
      'paged' => 1,
      'order' => 'desc',
      'tax_query' => array(
        array(
          'taxonomy' => $terms[0],
          'field' => 'slug',
          'terms' => $catSlug,
        )
      )
    ]);
  } else {
    $ajaxposts = new WP_Query([
      'post_type' => $catCpt,
      'posts_per_page' => 3,
      'offset' => $catOff,
      'orderby' => 'date',
      'paged' => 1,
      'order' => 'desc',
      'tax_query' => array(
        array(
          'taxonomy' => $terms[0],
          'field' => 'slug',
          'operator' => 'EXISTS'
        )
      )
    ]);
  }

  $response="";

  if($ajaxposts->have_posts()) {
    while($ajaxposts->have_posts()) : $ajaxposts->the_post();
      $image_data = wp_get_attachment_image_src( get_post_thumbnail_id( get_the_ID() ), "large" );
      $get_map_link = get_post_custom_values($key = 'map');
      $response .= '
      <div class="filtering-res-sin x-3">
        <div class="filtering-res-sin-top">
          <img src="' . $image_data[0] . '" width="' . $image_data[1] . '" height="' . $image_data[2] . '" alt="' . get_the_title() . '" />
        </div>
        <div class="filtering-res-sin-bot darkbg cropit vsb xx-anim">
          <div class="filtering-res-sin-bot-tit">' . get_the_title() . '</div>
          <div class="filtering-res-sin-bot-bar"></div>
          <div class="filtering-res-sin-bot-tog">Contact information <img src="https://lgkm.aero/wp-content/uploads/2022/11/load-more-plus-white-14x14-1-1.svg" width="12" height="12" alt="+" /></div>
          <div class="filtering-res-sin-bot-con">' . get_the_content() . '</div>
          <a class="filtering-res-sin-bot-but" href="' . (($get_map_link != '') ? $get_map_link[0] : '') . '"><img src="https://lgkm.aero/wp-content/uploads/2022/11/find-on-map-white-26x34-1.svg" width="26" height="34" alt="|" /> Find on map</a>
        </div>
      </div>';
    endwhile;

  } else {
    $response=""; ?>
  <?php }

  echo $response;
  exit;
}
add_action('wp_ajax_filtering_ajax_load', 'filtering_ajax_load');
add_action('wp_ajax_nopriv_filtering_ajax_load', 'filtering_ajax_load');

Then the JS part:

<script>
jQuery('.filtering .filtering-cpt-sin').on('click', function() {

  jQuery('.filtering .filtering-cpt-sin').removeClass('active');
    jQuery('.filtering-load').removeClass('dis');
  jQuery(this).addClass('active');

    jQuery('.filtering-load').attr('data-type',jQuery(this).attr('data-type'));
    jQuery('.filtering-load').attr('data-slug',jQuery(this).attr('data-slug'));
    jQuery('.filtering-load').attr('data-load',jQuery(this).attr('data-load'));

  jQuery.ajax({
    type: 'POST',
    url: '/wp-admin/admin-ajax.php',
    dataType: 'html',
    data: {
            type: jQuery(this).attr('data-type'),
            offset: 0,
      action: 'filtering_ajax',
      category: jQuery(this).attr('data-slug')
    },
    success: function(res) {
      jQuery('.filtering .filtering-res').html(res);
    }
  })
});

jQuery('.filtering .filtering-load').on('click', function() {
    jQuery.ajax({
    type: 'POST',
    url: '/wp-admin/admin-ajax.php',
    dataType: 'html',
    data: {
            type: jQuery(this).attr('data-type'),
            offset: jQuery('.filtering-res-sin').length,
      action: 'filtering_ajax_load',
      category: jQuery(this).attr('data-slug')
    },
    success: function(res) {
      jQuery('.filtering .filtering-res').append(res);
            if (jQuery('.filtering-res-sin').length >= jQuery('.filtering-load').attr('data-load')){
                jQuery('.filtering-load').addClass('dis');
            }
    }
  })
});
</script>

Some CSS to make it pretty:

.filtering {display: flex;justify-content: center;flex-direction: column;align-items: center;gap: 50px 0;margin-right: auto;margin-left: calc( var(--grid) + var(--gutter) );}
.filtering-cpt {display: flex;gap: 0 20px;}
.filtering-cpt a {font-family: Averta;font-size: 21px;font-weight: 600;line-height: 20px;color:#979797;transition:color .3s;}
.filtering-cpt a.active {color:#000;}
.filtering-res {display: flex;flex-wrap: wrap;gap: calc(var(--gutter) * 2);counter-reset:count;}
.filtering-res-sin {display: flex;flex-direction: column;color:#fff;counter-increment:count;}
.filtering-res-sin a {color:#fff;}
.filtering-res-sin-top {display: flex;flex-direction: column;}
.filtering-res-sin-top img {aspect-ratio: 4/3;object-fit: cover;object-position: center;}
.filtering-res-sin-bot {padding: 42px 50px 42px 60px;display: flex;flex-direction: column;gap: 17px;position:relative}
.filtering-res-sin-bot::after {content:counter(count, decimal-leading-zero);position:absolute;left:20px;top:25px;color:#fff;font-family: Roboto;font-size: 15px;font-weight: 400;line-height: 21px;}
.filtering-res-sin-bot-tit {font-family: Averta;font-size: 24px;font-weight: 600;line-height: 29px;}
.filtering-res-sin-bot-bar {background-color:#474B56;width:100%;height:1px;}
.filtering-res-sin-bot-tog {font-family: Averta;font-size: 17px;font-weight: 600;line-height: 20px;display:flex;align-items:center;gap:0 7px;}
.filtering-res-sin-bot-but {font-family: Averta;font-size: 20px;font-weight: 600;line-height: 24px;display:flex;align-items:center;gap:0 13px;}
.filtering-load {display:flex;justify-content:center;gap:0 7px;cursor:pointer;font-family: Averta;font-size: 21px;font-weight: 600;line-height: 20px;}
.filtering-load.dis {opacity:0.4;cursor:not-allowed;}

And finally call the one function that does the trick:

$filtering = array('cpt' => 'accommodation');
filtering($filtering);

Just change the accommodation to your own custom post type.