December 15, 2021

3 min read

Infinite scroll using IntersectionObserver and Ant design List component in ReactTs

Skeleton infinite loading

At my company, I was tasked to improve the UX of our web application by making use of infinite loading.
As our web app uses ant design to provide our users with an amazing and stunning UI, I had to look for a solution that is supported by ant design out of the box.
Unfortunately, there wasn’t anything like a prop that I could pass to the List component to get the infinite scroll working.
However, ant design uses react-infinite-scroll-component, a third-party library to demo the infinite scroll effect on their Scroll loaded example.

Despite the fact that ant design uses a third-party library to showcase the use of infinite scroll, it didn’t meet the requirements because the library kept on executing the load more function even when the last item is not visible on the viewport(screen). It caused a jumpy effect that was a failure in terms of improving the UX of the application.

Enough said 😋! Let’s dive into how we managed to provide a seamless infinite scroll behavior with the help of IntersectionObserver API to our web application.

Let’s start by putting in place the logic that we shall use to check if the last item is visible on the viewport.

1. Implement IntersectionObserver API

const observer = useRef<IntersectionObserver>();
const lastItemRef = useCallback(async (node: HTMLDivElement) => {
  if (loading) return;

   if (observer.current && items.length) {
     observer.current.disconnect();
     return;
   } 

   observer.current = new IntersectionObserver((entries) => {
     if (entries[0].isIntersecting && items.length) {
       onLoadMore();
      // Don't forget to disconnect the observer after your callback function 
       observer.current.disconnect();
    }
   });
   
  if (node) observer.current.observe(node);
 },
 [loading, items],);

Hold on 🖐! Let’s explain what’s going on here.

  • We declare a mutable value observer as useRef of type IntersectionObserver to persist the value and reference of the IntersectionObserver API on every render and to avoid re-rendering the component whenever its value changes.
  • We create a useCallback function to memoize the function and prevent re-rendering of the child component on every state change.
    This function takes a parameter, node of type HTMLDivElement which is the div element that we will be watching over on the viewport.
  • Inside the function, we have if (loading) return; which means nothing but stop executing if we are still loading the items.
    Furthermore, we added if (observer.current && items.length) to check if we are observing the element and have data in the items array. Then we observer.current.disconnect(); disconnect or stop the observer from watching over the element.

With that explanation out of the way, let’s move to the next step 😊.

2. Create a custom Ant design List component
export const InfiniteList = forwardRef<HTMLDivElement, IInfiniteList>(({
loading,
grid,
items,
renderItem,
onLoadMore,
}: IInfiniteList,
ref,
) => {
return (
<List
   loading={loading}
   loader={<div>Load more</div>}
   grid={grid}
   dataSource={items}
   renderItem={(item, index) => {
    if (index === items.length - 1) {
     return (
       <>
        {renderItem(item)}
        <div ref={ref} />
       </>
     );
    }
   return renderItem(item);
  }}
/>
);},);

Well! There are not enough magical kinds of stuff going here but let’s try to give some explanations.

  • We make use of forwardRef to get the reference of the element we want to observe. forwardRef is a react function that allows forwarding a component’s ref between one another.
  • In the renderItem function, we get the last item using if (index === items.length — 1) and then create an empty div that will be used as a reference for the infinite scroll. And lastly, we return the renderItem prop function for other items than the last one.
3. Pass the props
<InfiniteList
  items={items}
  loading={loading}
  grid={grid}
  renderItem={renderItem}
  onLoadMore={onLoadMore}
  ref={lastItemRef}
/>

And that’s pretty much it🤩 !

Let's build something together.

©Eliezer W. Basubi. All Rights Reserved.