javascripttypescriptpreact

IntersectionObserver with Preact lists


I have a preact application where I am displaying a list of items which are obtained from a server call. I am using functional components.

I need to make additional server calls when an item in the list is visible to the user, therefore I am looking at the IntersectionObserver.

Creating the IntersectionObserver

I can create the observer as follows (currently it just logs a console message when the item element comes into view).

const intersectionCallback = (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            console.log('IN OBSERVER', entry.target);
            observer.unobserve(entry.target);  // stop observing this item
        }
    });
}

const options = {
    root: document.getElementById('my-list-element-id'), 
    rootMargin: '0px',                              
    threshold: 0,                                   
};

const observer = new IntersectionObserver(intersectionCallback, options);

Observing items

Items can be observed using something like the following

const elements = document.querySelectorAll('list-view-row-element');
for (let i = 0; i < elements.length; i++) {
    observer.observe(elements[i]);
}

My list items are being rendered in a function inside my Function Component, they are not components themselves.

ISSUE

The issue I have is how/when to mark the rows as being observed.

In all the examples I have seen the HTML contains all the elements to observe, so "querySelectorAll" will find them as soon as the page is rendered.

In my example, the list rows will not be in the HTML until the server call has completed.

How do I know when the list rows have been added to the HTML in order to observe them?


Solution

  • You shouldn't be using document.querySelector (or other DOM selectors) w/ (p)react, it's not the way you're meant to be writing components.

    Generally, refs and useEffect is the answer:

    import { useEffect, useRef } from 'preact/hooks';
    
    function MyListEntry() {
        const ref = useRef(null);
    
        useEffect(() => {
            if (ref.current !== null) {
                observer.observe(ref.current);
            }
            return () => observer.unobserve(ref.current);
        }, []);
    
        return <list-view-row-element ref={ref}>...</list-view-row-element>;
    }