Infinite Hits, hasMore always false

HI all … having an issue implementing an infinite scroll. Everything seems to be functioning as expected (observer is calling the intersect method), but hasMore is always false. I’ve set the hits per page very low just for testing … all i ever get is 2 hits though …

Code for hits component:

import React, { useEffect, useRef } from 'react'
import { connectInfiniteHits } from 'react-instantsearch-dom'
import EventCard from './event-card'
import styled from 'styled-components'

const EventHits = connectInfiniteHits(({ hits, hasMore, refineNext }): JSX.Element => {
    const sentinel = useRef<HTMLDivElement | null>(null)

    const onSentinelIntersection = (entries: IntersectionObserverEntry[]) => {    
        entries.forEach((entry: IntersectionObserverEntry) => {
            console.log(hasMore)
            if (entry.isIntersecting && hasMore) {
                refineNext()
            }
        })
    }

    useEffect(
        () => {
            const initObserver = new IntersectionObserver(
                onSentinelIntersection
            )
            initObserver.observe(sentinel.current!)
            return () => initObserver.disconnect()
        }, [ sentinel ]
    )

    return (
        <>
            <HitsContainer>
                {hits.map((hit, index) => (
                    <EventCard event={hit} key={`event-${index}`} />
                ))}
            </HitsContainer>
            <div ref={sentinel} className="ais-InfiniteHits-sentinel"></div>
        </>
    )
})

const HitsContainer = styled.li`
    box-sizing: border-box;
    display: flex;
    flex-wrap: wrap;
    margin: 0 -8px;
`

export default EventHits

Implementation of the hits component:

const {	
        publicRuntimeConfig : {	
            ALGOLIA_APP_ID,	
            ALGOLIA_APP_KEY,
            ALGOLIA_APP_EVENTS_INDEX	
        }	
    } = getConfig()

    const Home = (): JSX.Element => {
        const searchClient = algoliasearch(ALGOLIA_APP_ID!, ALGOLIA_APP_KEY!)

        return (
            <Page>
                <Container>
                    <HomeContent>
                        <InstantSearch
                            searchClient={searchClient}
                            indexName={ALGOLIA_APP_EVENTS_INDEX!}
                        >
                            <EventHits />
                            <Configure hitsPerPage={2} />
                        </InstantSearch>
                    </HomeContent>
                </Container>
            </Page>
        )
    }

Hi @bfeeley,

The effects runs only two times: first render, second render (with sentinel). On the second render the effect capture the current scope and never updates it. On this render if the value of hasMore is false, it stays false for the rest of the lifecycle of the component.

This is a common issue with the usage of useEffect. You can fix this problem with the hasMore variable in the dependencies of the effect. It ensures that the effect is updated with the value. The downside is that the observer is created multiple times. I don’t think it’s a problem in that situation because the value won’t change that often. Here is an example (see below), the application code is available on CodeSandbox.

const Hits = ({ hits, refine, hasMore }) => {
  const ref = React.useRef();

  React.useEffect(() => {
    if (!hasMore) {
      // We don't have to create an observer when we don't have more
      // results to fetch from the API. Exit early.
      return;
    }

    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting && hasMore) {
          refine();
        }
      });
    });

    observer.observe(ref.current);

    return () => {
      observer.disconnect();
    };
  }, [hasMore]);

  return (
    <div>
      {hits.map(hit => (/* ... */))}
      <div ref={ref} />
    </div>
  );
};

Another alternative is to leverage useRef to save the hasMore value. You don’t have put the value in the dependencies and the observer won’t be created multiples times. Both solution should work the same.

Hope that helps!

1 Like

Perfect … thanks Samuel!