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>