0

I am working on a pure JavaScript infinite scroll with the help of the Intersection Observer API.

The scrollable items are posts from the jsonplaceholder.typicode.com API.

I load 5 posts initially, then 5 more with every scroll to the bottom.

class InfiniteScroll {
  constructor() {
    this.postsContainer = document.querySelector('#postsContainer');
    this.visiblePosts = [];
    this.postsLot = [];
    this.observer = null;
    this.hasNextPage = true;
    this.postsUrl = 'https://jsonplaceholder.typicode.com/posts';
    this.limit = 5;
    this.iterationCount = 0;
  }
  
  loadPosts() {
    fetch(this.postsUrl)
      .then(res => res.json())
      .then(posts => {
          this.postsLot = posts.slice(this.iterationCount * this.limit, this.limit);
          // Add items to the array of visible posts
          // with every iteration
          if(this.postsLot.length > 0) {
            this.postsLot.map(entry => {
                return this.visiblePosts.push(entry);
            });
          }
          this.renderVisiblePosts();
      })
      .catch(err => console.log(err));
  }
  
  renderVisiblePosts() {
    let output = '';
      this.visiblePosts.forEach(post => {
      output += `<div class="post">
                    <h2>${post.id} ${post.title}</h2>
                    <p>${post.body}</p>
                 </div>`;
    });
    this.postsContainer.innerHTML = output;
  }
  
  getLastPost() {
    return this.visiblePosts[this.visiblePosts.length - 1];
  }
  
  iterationCounter() {
    if (this.hasNextPage) {
      this.iterationCount = this.iterationCount + 1;
    }
  }
  
   bindLoadMoreObserver() {
    if (this.postsContainer) {
      this.observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
          if (entry && entry.isIntersecting) {
            console.log('bottom');
            observer.unobserve(entry.target);
              this.loadPosts();
              this.iterationCounter();
              if (this.hasNextPage) {
                observer.observe(this.getLastPost());
              }
          }
        });
      });

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

  init() {
    this.getLastPost();
    this.loadPosts();
    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;
}
<div id="postsContainer"></div>

The problem

Instead of observing when the last element comes into view, the browser throws the error:

Uncaught TypeError: Failed to execute 'observe' on 'IntersectionObserver': parameter 1 is not of type 'Element'.
Razvan Zamfir
  • 4,209
  • 6
  • 38
  • 252

1 Answers1

0

You're not observing the Element as per your current implementation. You're observing the last object in your visiblePosts array which is not an element.

You can get the last element by using this.postsContainer.lastElementChild provided that till then this.postsContainer has children Elements.

Lakshya Thakur
  • 8,030
  • 1
  • 12
  • 39
  • So, I should change `getLastPost()`? If yes, to what? – Razvan Zamfir Apr 13 '21 at 14:08
  • Since your question wanted to know the **cause** I have currently simply stated that. To actually make your code work, there are a couple of things to be handled. Firstly when you bind the intersection observer matters. You would need the `postsContainer` to have actual DOM elements so that you can select the last one using as I stated in my answer. Only then you can observe that last element. This also means that unless the new posts are loaded and new posts are rendered as elements, you cannot again observe the newly added last element. – Lakshya Thakur Apr 13 '21 at 14:14
  • I would have gone for a different implementation where there is a **placeholder element** as the last element of your list. Whenever that element intersects, I load more so that it goes again out of visible region. And then as I scroll down, it again comes into view and the same thing happens. – Lakshya Thakur Apr 13 '21 at 14:16
  • I used `this.postsContainer.lastElementChild`. It did not work. :( – Razvan Zamfir Apr 13 '21 at 15:12
  • Did you read my entire comment ? Just changing that simply won’t work. The placement of your IntersectionObserver matters. You can’t observe an element that is not there yet. The observation should only begin after your loadPosts returns new posts and you have called renderVisiblePosts(). I will see if I get time to change your code but I will probably implement this very differently. – Lakshya Thakur Apr 13 '21 at 15:19
  • This question asked by you seems to have a better implementation in its answers - https://stackoverflow.com/q/67004376/8130690 – Lakshya Thakur Apr 13 '21 at 15:26