> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Infinite scroll with InstantSearch.js

> Implement an infinite scroll experience with InstantSearch.js.

export const Index = () => <Tooltip tip="An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.">
    index
  </Tooltip>;

<div className="not-prose algolia-flavor-switcher">
  <div className="afs-dropdown">
    <div className="afs-trigger" role="button" tabIndex="0" aria-haspopup="listbox">
      <span className="afs-current">JavaScript</span>

      <svg className="afs-chevron lucide lucide-chevron-down" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
        <path d="m6 9 6 6 6-6" />
      </svg>
    </div>

    <ul className="afs-menu" role="listbox">
      <li role="option" aria-selected="true"><a className="afs-option is-current" href="/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/js"><span className="afs-option-name">JavaScript</span><span className="afs-option-lib">InstantSearch.js</span></a></li>
      <li role="option" aria-selected="false"><a className="afs-option" href="/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/react"><span className="afs-option-name">React</span><span className="afs-option-lib">React InstantSearch</span></a></li>
      <li role="option" aria-selected="false"><a className="afs-option" href="/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/vue"><span className="afs-option-name">Vue</span><span className="afs-option-lib">Vue InstantSearch</span></a></li>
      <li role="option" aria-selected="false"><a className="afs-option" href="/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/ios"><span className="afs-option-name">iOS</span><span className="afs-option-lib">InstantSearch iOS</span></a></li>
      <li role="option" aria-selected="false"><a className="afs-option" href="/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/android"><span className="afs-option-name">Android</span><span className="afs-option-lib">InstantSearch Android</span></a></li>
    </ul>
  </div>
</div>

An "infinite list" is a common way of displaying results. It's especially well-suited to mobile devices and has two variants:

* **Infinite hits** with a "See more" button at the end of a batch of results. Implement this with InstantSearch's [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) widget.
* **Infinite scroll** uses a listener on the scroll event (called when users have scrolled to the end of the first batch of results). The following guidance implements such an infinite scroll. Find the complete example on [GitHub](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/infinite-scroll).

<Note>
  If there are no results, you should [display a message to users and clear filters](/doc/guides/building-search-ui/going-further/conditional-display/js#handling-no-results) so they can start over.
</Note>

## Display a list of hits

Render the results with the [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) connector.

<Note>
  Read more about connectors in the [customizing widgets guide](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js).
</Note>

<Note>
  All examples in this guide assume you've included InstantSearch.js in your web page from a CDN. If, instead, you're using it with a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js) for more information.
</Note>

```js JavaScript icon=code theme={"system"}
const infiniteHits = instantsearch.connectors.connectInfiniteHits(
  (renderArgs, isFirstRender) => {
    const { items, showMore, widgetParams } = renderArgs;
    const { container } = widgetParams;

    if (isFirstRender) {
      container.appendChild(document.createElement('ul'));

      return;
    }

    container.querySelector('ul').innerHTML = items
      .map(
        item =>
          `<li>
            <div class="ais-Hits-item">
              <header class="hit-name">
                ${instantsearch.highlight({ attribute: 'name', : item })}
              </header>
              <img src="${hit.image}" align="left" />
              <p class="hit-description">
                ${instantsearch.highlight({ attribute: 'description', hit: item })}
              </p>
              <p class="hit-price">$${item.price}</p>
            </div>
          </li>`
      )
      .join('');
  }
);
```

## Track the scroll position

Once you have your results, the next step is to track the scroll position to determine when the rest of the content needs to be loaded (using the Intersection Observer API). Use the API to track when the bottom of the list (the "sentinel" element) enters the viewport. You can reuse the same element across different renders. The [Web Fundamentals](https://web.dev/articles/intersectionobserver/) website discusses the use of this API in more detail.

```js JavaScript icon=code theme={"system"}
const infiniteHits = instantsearch.connectors.connectInfiniteHits(
  (renderArgs, isFirstRender) => {
    const { items, showMore, widgetParams } = renderArgs;
    const { container } = widgetParams;

    if (isFirstRender) {
      const sentinel = document.createElement("div");
      container.appendChild(document.createElement("ul"));
      container.appendChild(sentinel);

      return;
    }

    // ...
  },
);
```

<Note>
  This implementation uses the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). To support [Internet Explorer 11](https://wikipedia.org/wiki/Internet_Explorer_11) you need a [polyfill](https://wikipedia.org/wiki/Polyfill_\(programming\)) for
  `IntersectionObserver`. A browser API is used in the example, but you can apply the concepts to any infinite scroll library.
</Note>

Once you have the reference to the "sentinel" element, create the Intersection Observer instance to determine when this element enters the page. Update the Intersection Observer's `options` object to adjust this code to your needs.

```js JavaScript icon=code theme={"system"}
const infiniteHits = instantsearch.connectors.connectInfiniteHits(
  (renderArgs, isFirstRender) => {
    const { items, showMore, widgetParams } = renderArgs;
    const { container } = widgetParams;

    if (isFirstRender) {
      const sentinel = document.createElement("div");
      container.appendChild(document.createElement("ul"));
      container.appendChild(sentinel);

      const observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            // In that case, you can refine
          }
        });
      });

      observer.observe(sentinel);

      return;
    }

    // ...
  },
);
```

## Retrieve more results

Now that you can track when you reach the end of the results, use the [showMore](/doc/api-reference/widgets/infinite-hits/js#param-show-more) function inside the callback function of the observer.
**Only trigger the function when there are still results to retrieve.**
For this use case, the connector provides a parameter [isLastPage](/doc/api-reference/widgets/infinite-hits/js#param-is-last-page) that indicates if you still have results.

```js JavaScript icon=code theme={"system"}
let lastRenderArgs;

const infiniteHits = instantsearch.connectors.connectInfiniteHits(
  (renderArgs, isFirstRender) => {
    const { items, showMore, widgetParams } = renderArgs;
    const { container } = widgetParams;

    lastRenderArgs = renderArgs;

    if (isFirstRender) {
      const sentinel = document.createElement("div");
      container.appendChild(document.createElement("ul"));
      container.appendChild(sentinel);

      const observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting && !lastRenderArgs.isLastPage) {
            showMore();
          }
        });
      });

      observer.observe(sentinel);

      return;
    }

    // ...
  },
);
```

## Show more than 1,000 hits

To ensure excellent performance, the default limit for the number of hits you can retrieve for a query is
1,000.

<CodeGroup>
  ```cs C# theme={"system"}
  var response = await client.SetSettingsAsync(
    "INDEX_NAME",
    new IndexSettings { PaginationLimitedTo = 1000 }
  );
  ```

  ```dart Dart theme={"system"}
  final response = await client.setSettings(
    indexName: "INDEX_NAME",
    indexSettings: IndexSettings(
      paginationLimitedTo: 1000,
    ),
  );
  ```

  ```go Go theme={"system"}
  response, err := client.SetSettings(client.NewApiSetSettingsRequest(
    "INDEX_NAME",
    search.NewEmptyIndexSettings().SetPaginationLimitedTo(1000)))
  if err != nil {
    // handle the eventual error
    panic(err)
  }
  ```

  ```java Java theme={"system"}
  UpdatedAtResponse response = client.setSettings("INDEX_NAME", new IndexSettings().setPaginationLimitedTo(1000));
  ```

  ```js JavaScript theme={"system"}
  const response = await client.setSettings({
    indexName: 'theIndexName',
    indexSettings: { paginationLimitedTo: 1000 },
  });
  ```

  ```kotlin Kotlin theme={"system"}
  var response =
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(paginationLimitedTo = 1000),
    )
  ```

  ```php PHP theme={"system"}
  $response = $client->setSettings(
      'INDEX_NAME',
      ['paginationLimitedTo' => 1000,
      ],
  );
  ```

  ```python Python theme={"system"}
  response = client.set_settings(
      index_name="INDEX_NAME",
      index_settings={
          "paginationLimitedTo": 1000,
      },
  )
  ```

  ```ruby Ruby theme={"system"}
  response = client.set_settings("INDEX_NAME", Algolia::Search::IndexSettings.new(pagination_limited_to: 1000))
  ```

  ```scala Scala theme={"system"}
  val response = Await.result(
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(
        paginationLimitedTo = Some(1000)
      )
    ),
    Duration(100, "sec")
  )
  ```

  ```swift Swift theme={"system"}
  let response = try await client.setSettings(
      indexName: "INDEX_NAME",
      indexSettings: IndexSettings(paginationLimitedTo: 1000)
  )
  ```
</CodeGroup>

If you need to show more than 1,000 hits, you can set a different limit using the [`paginationLimitedTo`](/doc/api-reference/api-parameters/paginationLimitedTo) parameter.
The higher you set this limit, the slower your search performance can become.

<Note>
  Increasing the limit doesn't mean you can go until the end of the hits,
  but just that Algolia will go as far as possible in the <Index /> to retrieve results in a reasonable time.
</Note>
