Pure JavaScript infinite scroll I have a 4 list of posts. I am trying to load more posts (clones of the &quot;original&quot; ones) when I reach the bottom of the container.


class InfiniteScroll {
  constructor() {
    this.observer = null;
    this.isLoading = false;
    this.postsContainer = document.querySelector('#postsContainer');
    this.postsArary = postsContainer.querySelectorAll('.post');
    this.iterationCount = Number(this.postsContainer.dataset.currentPage);
    this.perPage = 5;
    this.maxCount = this.postsContainer?.dataset?.maxCount;
    this.numberOfPages = Math.ceil(this.maxCount / this.perPage);
    this.hasNextPage = this.iterationCount &lt; this.numberOfPages;
  }

  loadMorePosts() {
    if (this.hasNextPage) {
      this.postsArary.forEach(item =&gt; {
        let postClone = item.cloneNode(true);
        this.postsContainer.appendChild(postClone);
      });
    } else {
      if (this.observer) this.observer.disconnect();
    }
  }

  getLastPost() {
    const allPosts = postsContainer.querySelectorAll('.post');
    return allPosts[allPosts.length - 1];
  }

  counter() {
    if (this.hasNextPage) {
      this.iterationCount = this.iterationCount + 1;
      this.postsContainer.dataset.currentPage = this.iterationCount;
    }

    this.postsContainer.dataset.hasNextPage = this.hasNextPage;
    this.hasNextPage = this.iterationCount &lt; this.numberOfPages - 1;
  }

  bindLoadMoreObserver() {
    if (this.postsContainer) {
      this.observer = new IntersectionObserver((entries, observer) =&gt; {
        entries.forEach(entry =&gt; {
          if (entry &amp;&amp; entry.isIntersecting) {
            observer.unobserve(entry.target);
            this.loadMorePosts();
            this.counter();
            if (this.hasNextPage) {
              observer.observe(this.getLastPost());
            }
          }
        });
      });

      this.observer.observe(this.getLastPost());
    }
  }

  init() {
    this.bindLoadMoreObserver();
  }
}

const infiniteScroll = new InfiniteScroll();
infiniteScroll.init();
body, body * {
  margin: 0;
  padding: 0;
}

body {
  font-family: Arial, Helvetica, sans-serif;
}

.post {
  margin: 20px;
  padding: 15px;
  border: 1px solid #ccc;
  border-radius: 5px;
}

p {
  line-height: 1.5;
}
&lt;div id="postsContainer" data-has-next-page="true" data-current-page="0" data-max-count="11"&gt;
  &lt;div class="post"&gt;
    &lt;h2&gt;Title 1&lt;/h2&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque dolorum consequatur nostrum sapiente ipsa! Veniam, laudantium accusantium, odio maxime quo adipisci possimus enim quam, voluptate quidem animi perferendis delectus aliquam?&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class="post"&gt;
    &lt;h2&gt;Title 2&lt;/h2&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque dolorum consequatur nostrum sapiente ipsa! Veniam, laudantium accusantium, odio maxime quo adipisci possimus enim quam, voluptate quidem animi perferendis delectus aliquam?&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class="post"&gt;
    &lt;h2&gt;Title 3&lt;/h2&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque dolorum consequatur nostrum sapiente ipsa! Veniam, laudantium accusantium, odio maxime quo adipisci possimus enim quam, voluptate quidem animi perferendis delectus aliquam?&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class="post"&gt;
    &lt;h2&gt;Title 4&lt;/h2&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque dolorum consequatur nostrum sapiente ipsa! Veniam, laudantium accusantium, odio maxime quo adipisci possimus enim quam, voluptate quidem animi perferendis delectus aliquam?&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
Too complex
Always strive to keep source code complexity as low as possible.
The implementation of InfiniteScroll is way too complex.
It should not be an instantiatable object as it only works if there is one instance, and will only work once.

Page counting need only one variable not 3    this.maxCount, this.numberOfPages and this.hasNextPage can be replace with a single counter. See rewrite pageCount which counts down.

There is no need to store iterationCount or hasNextPage in markup. this.postsContainer.dataset.currentPage = this.iterationCount;

Use Pseudo-class &quot;:last-child&quot; to select last post. querySelector(&quot;.post:last-child&quot;)

In function getLastPost you reference an undefined variable const allPosts = postsContainer.querySelectorAll(&quot;.post&quot;);  should be const allPosts = this.postsContainer.querySelectorAll(&quot;.post&quot;);

The second argument of the IntersectionObserver is a reference to the same object you already have a reference to, there is no need to use the second argument.

When the observer callback is called there will only be one entry that you need to handle. Rather than entries.forEach use entries.find to locate the entry.


Rewrite
The rewrite does not create an object. The function InfiniteScroll holds the observer. When observer.disconnect() is called the IntersectionObserver callback will be de-referenced and the closure containing the InfiniteScroll will be cleaned up automatically.
To prevent InfiniteScroll from being called again it is de-referenced on the first call.
It works the same but with over 65% less code


function InfiniteScroll() {
    InfiniteScroll = undefined;
    const query = (qStr, root = document) =&gt; root.querySelector(qStr);
    const lastPost = () =&gt; query(".post:last-child", container);

    const perPage = 5;
    const container = query("#postsContainer");
    const posts = container.querySelectorAll(".post");
    var pageCount = container.dataset.maxCount / perPage | 0;

    const observer = new IntersectionObserver(intersect);
    observer.observe(lastPost()); 

    function intersect(entries) {
        const intersecting = entries.find(entry =&gt; entry.isIntersecting);
        if (intersecting) {
            observer.unobserve(intersecting.target);
            posts.forEach(item =&gt; container.appendChild(item.cloneNode(true))); 
            --pageCount ? observer.observe(lastPost()) : observer.disconnect();
        }
    }
}

InfiniteScroll();
body, body * {
  margin: 0;
  padding: 0;
}

body {
  font-family: Arial, Helvetica, sans-serif;
}

.post {
  margin: 20px;
  padding: 15px;
  border: 1px solid #ccc;
  border-radius: 5px;
}

p {
  line-height: 1.5;
}
&lt;div id="postsContainer" data-has-next-page="true" data-current-page="0" data-max-count="11"&gt;
  &lt;div class="post"&gt;
    &lt;h2&gt;Title 1&lt;/h2&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque dolorum consequatur nostrum sapiente ipsa! Veniam, laudantium accusantium, odio maxime quo adipisci possimus enim quam, voluptate quidem animi perferendis delectus aliquam?&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class="post"&gt;
    &lt;h2&gt;Title 2&lt;/h2&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque dolorum consequatur nostrum sapiente ipsa! Veniam, laudantium accusantium, odio maxime quo adipisci possimus enim quam, voluptate quidem animi perferendis delectus aliquam?&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class="post"&gt;
    &lt;h2&gt;Title 3&lt;/h2&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque dolorum consequatur nostrum sapiente ipsa! Veniam, laudantium accusantium, odio maxime quo adipisci possimus enim quam, voluptate quidem animi perferendis delectus aliquam?&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class="post"&gt;
    &lt;h2&gt;Title 4&lt;/h2&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque dolorum consequatur nostrum sapiente ipsa! Veniam, laudantium accusantium, odio maxime quo adipisci possimus enim quam, voluptate quidem animi perferendis delectus aliquam?&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;