DEV Community

Victor Magarlamov
Victor Magarlamov

Posted on

Infinite Scrolling with IntersectionObserver

Infinite Scrolling is one of the technique that is used to automatically load more content when the user scrolls down the page.

The idea of ​​the solution that I will show you in this article is quite simple. We will add an anchor to the end of the list. It can be an animated preloader, or label, or just an empty div. When the feed scrolls down, the anchor will rise. When the anchor crosses the lower boundary of the feed, we will show new data, and the anchor will lower again.

Let’s write a simple Feed component.

class NewsFeed extends Component {
  static perPage = 50;

  allNews = [];

  state = {
    page: 0,
  };

  componentDidMount() {
    fetchNews().then(res => this.allNews = res);
  }

  getVisibleNews() {
    return this.allNews.slice(0, this.state.page * NewsFeed.perPage);
  }

  render() {
    return (
      <div className=feed>
        {this.getVisibleItems().map(item => (
          <FeedItem key={item.id} item={item} />
        ))}
        <div>Loading</div>
      </div>
    );
  }
}

When we do this, we will see a “Loading“ label. Then the data will be downloaded and... And nothing will change. The page will only have a “Loading” label.

When does re-rendering happen in a React app? When the state has been changed. When the props have been changed. When the parent has changed. In our case none of this happened. The allNews property was changed only.

Well, it’s time to add some logic to check the position of the loader. To do this, I will add an observer to the component.

Observer is a behavior pattern, which allows us to receive notifications when a target changes. The Web APIs has the Intersection Observer API that allows us to observe how the target intersects with the parent element.

One important nuance! To asynchronously observe and it is the crucial difference between regular Event and Observer.

To add an observer to asynchronously observe the intersection of the Loader with the Feed element, we need to create an instance of IntersectionObserver. There are two parameters which we must pass to the IntersectionObserver constructor: a callback that will be fired when the intersection occurs and a config - an object with the following properties: root - the ancestor element (by default, this is a browser viewer), rootMargin - it is like the margins in CSS, threshold - a single number or an array of values which allow us to customize callback execution moments.

initIntersectionObserver() {
  const config = {
    root: document.querySelector(.feed),
    rootMargin: 0px,
    threshold: 0,
  };

  let observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        this.setState(prevState => ({
          page: prevState.page + 1
        }));
      } 
    });
  }, config);

  observer.observe(document.querySelector(.loader));
}

Let's call this function when the data is loaded.

componentDidMount() {
  fetchNews().then(res => {
    this.allNews = res;
    this.initIntersectionObserver();
  });
}

As soon as we set the target element for observation using observer.observe, the callback will be executed for the first time. At the moment, the preloader is inside the Feed element. They intersect and the entries will be contain an entry with the isIntersecting property equal to ‘true’. The state will be updated and we will see the news. When the anchor goes down the border of the Feed element, we get an object with the isIntersecting property equal to false.

When the user scrolls the news and the loader will be in the viewing area... You know what will happen next 🔥

More about the Intersection Observer API...

Top comments (0)