Infinite Scroll for Ionic/React

On one of the Ionic/React applications, I'm working on, we needed an Infinite Scroll component. Currently, Ionic's ion-infinite-scroll is not available as a React component, so we had to hack the solution by ourselves.

The only available component that gives us access to the scrolling in Ionic/React is IonContent. It gives us access to the onIonScroll event with the current scrollTop value.

<IonPage> 
  ... 
  <IonContent 
    scrollEvents={true} 
    onIonScroll={(evt) => { console.log(evt.detail.scrollTop} } 
  > 
    ... 
  </IonContent> 
</IonPage>

But to determine when we are approaching the scroll end and load some additional data, we need to know two more values: the height of the container and the height of the content. In plain HTML these are the properties clientHeight and scrollHeight of the element with overflow set to scroll or auto. To get them in React we'll have to have a reference to the component.

const contentRef = useRef(null); 

const scrollContent = evt => { 
  console.log( 
    evt.detail.scrollTop, 
    contentRef.current.clientHeight, 
    contentRef.current.scrollHeight, 
  ) 
} 
... 
<IonContent 
  scrollEvents={true} 
  onIonScroll={scrollContent} 
  ref={contentRef} 
> 
  ... 
</IonContent>

But... this will not work. The scrollHeight is always equal to the clientHeight. Because IonContent is a web component and the actual scrolling element is inside the Shadow DOM (<main part="scroll">).

Hopefully, we can get access to this element with a method from Ionic/React – getScrollElement(). But it returns a Promise and we cannot use it inside the scrollContent function.

The Result

After trying a few different configurations with these values, we came down to the variant you can find at the end of this post.

Assume articles is a state with the things we are displaying and we have to load new articles if not everything is loaded yet.

We are keeping references to both IonContent and the actual scrolling content. We are getting the reference to the actual scrolling content once the data is loaded.

Our logic is to load additional articles, one screen before the user will reach the end:

scrollTop + clientHeight * 2 > scrollHeight

Be careful: scrolling is producing a lot of events. And you have to make sure you are not trying to initiate new loading every time the event is firing. In our case, we are keeping track of the status and loading only when the status === 'LOADED'. You will have different checks. Just make sure there is a check for this.

const contentRef = useRef(null);
const scrollRef = useRef(null);

useEffect(() => {
  if (contentRef.current && contentRef.current.getScrollElement) {
    contentRef.current.getScrollElement().then(res => {
      scrollRef.current = res;
    });
  }
}, [articles]);

const scrollContent = (evt: any) => {
  if (
    articles.status === 'LOADED' &&
    articles.total > articles.loaded &&
    scrollRef.current &&
    scrollRef.current.scrollHeight <
      evt.detail.scrollTop + scrollRef.current.clientHeight * 2
  ) {
    loadMoreArticles();
  }
}
...
<IonContent
  scrollEvents={true}
  onIonScroll={scrollContent}
  ref={contentRef}
>
... 
</IonContent>