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

# ais-infinite-hits

> Shows search results with a button to load more.

export const Records = () => <Tooltip tip="A record is a searchable object in an Algolia index. Each record consists of named attributes." cta="Algolia records" href="/doc/guides/sending-and-managing-data/prepare-your-data#algolia-records">
    records
  </Tooltip>;

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>;

```vue Signature theme={"system"}
<ais-infinite-hits
  // Optional parameters
  :show-banner="boolean"
  :escapeHTML="boolean"
  :show-previous="boolean"
  :class-names="object"
  :transform-items="function"
  :cache="object"
/>
```

## Import

<Tabs>
  <Tab title="Component">
    To ensure optimal bundle sizes,
    see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size).

    ```js Vue icon=code theme={"system"}
    import { AisInfiniteHits } from "vue-instantsearch";
    // Use "vue-instantsearch/vue3/es" for Vue 3

    export default {
      components: {
        AisInfiniteHits
      },
      // ...
    };
    ```
  </Tab>

  <Tab title="Plugin">
    This imports all widgets, even the ones you don't use.
    For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue).

    ```js JavaScript icon="code" theme={"system"}
    import Vue from "vue";
    import InstantSearch from "vue-instantsearch";
    // Use "vue-instantsearch/vue3/es" for Vue 3

    Vue.use(InstantSearch);
    ```
  </Tab>
</Tabs>

<Card title="See this widget in action" icon="monitor-play" href="https://instantsearchjs.netlify.app/stories/vue/?selectedKind=ais-infinite-hits" horizontal>
  Preview this widget and its behavior.
</Card>

## About this widget

The `ais-infinite-hits` widget displays a list of results with a "Show more" button at the bottom of the list.
As an alternative to this approach,
[the infinite scroll guide](/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/vue)
describes how to create an automatically scrolling infinite hits experience.

To configure the number of hits to show, use the [`ais-hits-per-page`](/doc/api-reference/widgets/hits-per-page/vue)
or the [`ais-configure`](/doc/api-reference/widgets/configure/vue) widget.

See also: [Searches without results](/doc/guides/building-search-ui/going-further/conditional-display/vue#handling-no-results).

## Examples

```vue Vue icon=code theme={"system"}
<ais-infinite-hits />
```

## Props

<ParamField body="show-banner" type="boolean" default={true}>
  Whether to display a top banner when banner data is included within the `renderingContent` property from the Algolia API response.

  ```vue Vue icon=code theme={"system"}
  <ais-infinite-hits :show-banner="true" />
  ```
</ParamField>

<ParamField body="escapeHTML" type="boolean" default={true}>
  Whether to escape the raw HTML in the hits.

  ```vue Vue icon=code theme={"system"}
  <ais-infinite-hits :escapeHTML="false" />
  ```
</ParamField>

<ParamField body="show-previous" type="boolean" default={false}>
  Enable the button to load previous results.
  The button is only displayed if the routing option is enabled in [`ais-instant-search`](/doc/api-reference/widgets/instantsearch/vue#param-routing) and users aren't on the first page.
  Override this behavior with [slots](/doc/api-reference/widgets/infinite-hits/vue#customize-the-ui).

  ```vue Vue icon=code theme={"system"}
  <ais-infinite-hits :show-previous="true" />
  ```
</ParamField>

<ParamField body="class-names" type="object" default="{}">
  The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets):

  * `ais-InfiniteHits`. The root element of the widget.
  * `ais-InfiniteHits-list`. The list of results.
  * `ais-InfiniteHits-item`. The list items.
  * `ais-InfiniteHits-banner`. The optional banner's root.
  * `ais-InfiniteHits-banner-image`. The image element of the optional banner.
  * `ais-InfiniteHits-banner-link`. The optional anchor element of the optional banner.
  * `ais-InfiniteHits-loadPrevious`. The button to display previous results.
  * `ais-InfiniteHits-loadMore`. The button to display more results.
  * `ais-InfiniteHits-loadPrevious--disabled`. The disabled button to display previous results.
  * `ais-InfiniteHits-loadMore--disabled`. The disabled button to display more results.

  ```vue Vue icon=code theme={"system"}
  <ais-infinite-hits
    :class-names="{
      'ais-InfiniteHits': 'MyCustomInfiniteHits',
      'ais-InfiniteHits-list': 'MyCustomInfiniteHitsList',
      // ...
    }"
  />
  ```
</ParamField>

<ParamField body="transform-items" type="function" default="items => items">
  Receives the items and is called before displaying them.
  It returns a new array with the same "shape" as the original.
  This is helpful when transforming, removing, or reordering items.

  The complete `results` data is also available,
  including all [regular response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response) and [helper parameters](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters) (for example, `disjunctiveFacetsRefinements`).

  If you're transforming an attribute with the [`ais-highlight`](/doc/api-reference/widgets/highlight/vue) widget,
  you must transform `item._highlightResult[attribute].value`.

  <Note>
    To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops),
    avoid passing arrays, objects, or functions directly in the template.
    These values aren't referentially equal on each render,
    which causes the widget to re-register every time.
    Instead, define them in your component's `data` option and reference them in the template.
  </Note>

  ```vue Vue icon=code theme={"system"}
  <template>
    <!-- ... -->
    <ais-infinite-hits :transform-items="transformItems" />
  </template>

  <script>
  export default {
    methods: {
      transformItems(items) {
        return items.map((item) => ({
          ...item,
          name: item.name.toUpperCase(),
        }));
      },

      /* or, combined with results */
      transformItems(items, { results }) {
        return items.map((item, index) => ({
          ...item,
          position: { index, page: results.page },
        }));
      },
    },
  };
  </script>
  ```
</ParamField>

<ParamField body="cache" type="object" default="in-memory cache object">
  The widget caches all loaded hits.
  By default, it uses its own internal in-memory cache implementation.
  Alternatively, use [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) to retain the cache even if users reload the page.

  You can also implement your own cache object with `read` and `write` methods.
  This can be handy if you need to persist the data across sessions or if you expect the cached data to grow larger than the browser's [5 MB allowed storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria#web_storage).

  <CodeGroup>
    ```vue sessionStorage theme={"system"}
    <template>
      <ais-infinite-hits :cache="cache" />
    </template>

    <script>
    import { createInfiniteHitsSessionStorageCache } from "instantsearch.js/es/lib/infiniteHitsCache";

    export default {
      data() {
        return {
          cache: createInfiniteHitsSessionStorageCache(),
        };
      },
    };
    </script>
    ```

    ```vue custom theme={"system"}
    <template>
      <ais-infinite-hits :cache="cache" />
    </template>

    <script>
    const customCache = {
      read({ state }) {
        const { cachedKey, cachedHits } = // read from your cache
        const currentKey = convertStateToKey(state);
        if (isEqual(cachedKey, currentKey)) {
          return cachedHits;
        } else {
          return null;
        }
      },
      write({ state, hits }) {
        const currentKey = convertStateToKey(state);
        // write in your cache
      },
    }

    function isEqual(obj1, obj2) {
      // perform deep equal
    }

    function convertStateToKey(state) {
      const { page, ...rest } = state || {};
      // Regardless of the `page`,
      // check if the rest of the state is the same.
      return rest;
    }

    export default {
      data() {
        return {
          cache: customCache,
        }
      }
    }
    </script>
    ```
  </CodeGroup>
</ParamField>

## Customize the UI

<ParamField body="default">
  The slot to override the complete DOM output of the widget.

  When you implement this slot, none of the other slots will change the output, as the default slot surrounds them.

  **Scope**

  * `items: object[]`.
    The <Records /> that matched the search.
    Each element of `items` has the following read-only properties:

    * `__queryID`. The query ID (only if `clickAnalytics` is `true`).
    * `__position`. The absolute position of the item.

  * `banner: object`. The banner data returned within the `renderingContent` property from the Algolia API response.

  * `refinePrevious: () => void`. The function to load previous results.

  * `refineNext: () => void`. The function to load more results.

  * `isLastPage: boolean`. Whether it's the last page.

  * `sendEvent: (eventType, hit, eventName) => void`.
    The function to send `click` or `conversion` events.
    The `view` event is automatically sent when this connector renders hits.
    To learn more, see the [`insights`](/doc/api-reference/widgets/insights/vue) middleware.

    * `eventType: 'click' | 'conversion'`
    * `hit: Hit | Hit[]`
    * `eventName: string`

  * `insights: (method: string, payload: object) => void`: (Deprecated) Sends Insights events.

    * `method: string`. The Insights method to be called. Only search-related methods are supported: `'clickedObjectIDsAfterSearch'`, `'convertedObjectIDsAfterSearch'`.
    * `payload: object`. The payload to be sent.

      * `eventName: string`. The name of the event.
      * `objectIDs: string[]`. A list of `objectID`s.
      * `index?: string`. The name of the <Index /> related to the click.
      * `queryID?: string`. The Algolia `queryID` found in the search response when `clickAnalytics: true`.
      * `userToken?: string`. A user identifier.
      * `positions?: number[]`. The position of the click in the list of Algolia search results.

        When not provided, `index`, `queryID`, and `positions` are inferred by the InstantSearch context and the passed object IDs:

        * `index` by default is the name of index that returned the passed object IDs.
        * `queryID` by default is the ID of the query that returned the passed object IDs.
        * `positions` by default is the absolute positions of the passed object IDs.

        <Note>
          For more details about the `payload` property, see the [Insights client documentation](/doc/libraries/search-insights).
        </Note>

  <CodeGroup>
    ```vue items theme={"system"}
    <ais-infinite-hits>
      <template v-slot="{
        items,
        banner,
        refinePrevious,
        refineNext,
        isLastPage,
        sendEvent,
      }">
        <img :src="banner.image.urls[0].url" alt="banner alt text" />
        <ul>
          <li>
            <button @click="refinePrevious">
              Show previous results
            </button>
          </li>
          <li v-for="item in items" :key="item.objectID">
            <h1>{{ item.name }}</h1>
            <button
              type="button"
              @click="sendEvent('click', item, 'Item Added')"
            >
              Add to cart
            </button>
          </li>
          <li v-if="!isLastPage">
            <button @click="refineNext">
              Show more results
            </button>
          </li>
        </ul>
      </template>
    </ais-infinite-hits>
    ```

    ```vue insights theme={"system"}
    <ais-infinite-hits>
      <template v-slot="{ items, insights }">
        <ul>
          <li v-for="item in items" :key="item.objectID">
            <h1>{{item.name}}</h1>
            <button
              @click="insights(
                'clickedObjectIDsAfterSearch',
                { eventName: 'Add to cart', objectIDs: [item.objectID] }
              )"
            >
              Add to cart
            </button>
          </li>
        </ul>
      </template>
    </ais-infinite-hits>
    ```
  </CodeGroup>
</ParamField>

<ParamField body="loadPrevious">
  The slot to override the DOM output of the "Show previous" button.

  **Scope**

  * `page: number`: the value of the current page.
  * `isFirstPage: boolean`: whether it's the first page.
  * `refinePrevious: () => void`: the function to load the previous page.

  ```vue Vue icon=code theme={"system"}
  <ais-infinite-hits :show-previous="true">
    <template v-slot:loadPrevious="{ page, isFirstPage, refinePrevious }">
      <button
        :disabled="isFirstPage"
        @click="refinePrevious"
      >
        Show previous results (page: {{ page + 1 }})
      </button>
    </template>
  </ais-infinite-hits>
  ```
</ParamField>

<ParamField body="banner">
  The slot to override the DOM output of the banner.

  **Scope**

  * `banner: object`. The banner data returned within the `renderingContent` property from the Algolia API response.

  ```vue Vue icon=code theme={"system"}
  <ais-infinite-hits>
    <template v-slot:banner="{ banner }">
      <img :src="banner.image.urls[0].url" alt="banner alt text" />
    </template>
  </ais-infinite-hits>
  ```
</ParamField>

<ParamField body="item">
  The slot to override the DOM output of the item.

  **Scope**

  * `items: object`. A single hit with all its attributes.
    Each element of `items` has the following read-only properties:

    * `__queryID`. The query ID (only if `clickAnalytics` is `true`).
    * `__position`. The absolute position of the item.

  * `index: number`. The relative position of the hit in the list.

  * `insights: (method: string, payload: object) => void`. (Deprecated) sends Insights events.

    * `method: string`. The Insights method to be called. Only search-related methods are supported: `'clickedObjectIDsAfterSearch'`, `'convertedObjectIDsAfterSearch'`.
    * `payload: object`. The payload to be sent.

      * `eventName: string`. The name of the event.
      * `objectIDs: string[]`. A list of object IDs.
      * `index?: string`. The name of the index related to the click.
      * `queryID?: string`. The Algolia `queryID` found in the search response when `clickAnalytics: true`.
      * `userToken?: string`. A user identifier.
      * `positions?: number[]`. The position of the click in the list of Algolia search results.

        When not provided, `index`, `queryID`, and `positions` are inferred by the InstantSearch context and the passed `objectIDs`:

        * `index` by default is the name of index that returned the passed `objectIDs`.
        * `queryID` by default is the ID of the query that returned the passed `objectIDs`.
        * `positions` by default is the absolute positions of the passed `objectIDs`.

      <Note>
        For more details about the `payload` property, see the [Insights client documentation](/doc/libraries/search-insights).
      </Note>

  ```vue Vue icon=code theme={"system"}
  <ais-infinite-hits>
    <template v-slot:item="{ item, index, insights }">
      {{ index }} - {{ item.name }}
    </template>
  </ais-infinite-hits>
  ```
</ParamField>

<ParamField body="loadMore">
  The slot to override the DOM output of the "Show more" button.

  **Scope**

  * `page: number`. The value of the current page.
  * `isLastPage: boolean`. Whether it's the last page.
  * `refineNext: () => void`. The function to load the next page.

  ```vue Vue icon=code theme={"system"}
  <ais-infinite-hits>
    <template v-slot:loadMore="{ page, isLastPage, refineNext }">
      <button
        :disabled="isLastPage"
        @click="refineNext"
      >
        Show more results (page: {{ page + 1 }})
      </button>
    </template>
  </ais-infinite-hits>
  ```
</ParamField>

## HTML output

```html HTML icon=code-xml theme={"system"}
<div class="ais-InfiniteHits">
  <button class="ais-InfiniteHits-loadPrevious">Show previous results</button>
  <aside class="ais-Hits-banner">
    <a class="ais-Hits-banner-link">
      <img class="ais-Hits-banner-image" />
    </a>
  </aside>
  <ol class="ais-InfiniteHits-list">
    <li class="ais-InfiniteHits-item">...</li>
    <li class="ais-InfiniteHits-item">...</li>
    <li class="ais-InfiniteHits-item">...</li>
  </ol>
  <button class="ais-InfiniteHits-loadMore">Show more results</button>
</div>
```

## Click and conversion events

If the [`insights`](/doc/api-reference/widgets/instantsearch/vue#param-insights) option is `true`,
the `ais-infinite-hits` widget automatically sends a `click` event with the following "shape" to the Insights API when a user clicks on a hit.

```json JSON icon=braces theme={"system"}
{
  "eventType": "click",
  "insightsMethod": "clickedObjectIDsAfterSearch",
  "payload": {
    "eventName": "Hit Clicked"
    // …
  },
  "widgetType": "ais.infiniteHits"
}
```

To customize this event, use the `sendEvent` function in your [`item`](#param-item) slot and send a custom `click` event.

```vue Vue icon=code theme={"system"}
<ais-infinite-hits>
  <template v-slot:item="{ item, sendEvent }">
    <div @click="sendEvent('click', item, 'Product Clicked')">
      <h2>
        <ais-highlight attribute="name" :hit="item" />
      </h2>
      <p>{{ item.description }}</p>
    </div>
  </template>
</ais-infinite-hits>
```

The `sendEvent` function also accepts an object as a fourth argument to send directly to the Insights API.
You can use it, for example, to send special `conversion` events with a subtype.

```vue Vue icon=code theme={"system"}
<ais-infinite-hits>
  <template v-slot:item="{ item, sendEvent }">
    <div>
      <h2>
        <ais-highlight attribute="name" :hit="item" />
      </h2>
      <p>{{ item.description }}</p>
      <button
        @click="sendEvent('conversion', hit, 'Added To Cart', {
          // Special subtype
          eventSubtype: 'addToCart',
          // An array of objects representing each item added to the cart
          objectData: [
            {
              // The discount value for this item, if applicable
              discount: item.discount || 0,
              // The price value for this item (minus the discount)
              price: item.price,
              // How many of this item were added
              quantity: 2,
            },
          ],
          // The total value of all items
          value: item.price * 2,
          // The currency code
          currency: 'USD',
        })"
      >
        Add to cart
      </button>
    </div>
  </template>
</ais-hits>
```

<Note>
  Use strings to represent monetary values in major currency units (for example, '5.45').
  This avoids floating-point rounding issues, especially when performing calculations.
</Note>

See also: [Send click and conversion events with Vue InstantSearch](/doc/guides/building-search-ui/events/vue)
