Skip to content

Infinite Scroll

inertia-sails provides sails.inertia.scroll() for implementing infinite scroll with pagination metadata.

Basic Usage

js
module.exports = {
  inputs: {
    page: {
      type: 'number',
      defaultsTo: 0
    }
  },

  exits: {
    success: { responseType: 'inertia' }
  },

  fn: async function ({ page }) {
    const perPage = 20
    const invoices = await Invoice.find()
      .sort('createdAt DESC')
      .paginate(page, perPage)
    const total = await Invoice.count()

    return {
      page: 'invoices/index',
      props: {
        invoices: sails.inertia.scroll(() => invoices, {
          page,
          perPage,
          total
        })
      }
    }
  }
}

Options

OptionTypeDescription
pagenumberCurrent page (0-indexed)
perPagenumberItems per page
totalnumberTotal item count
wrapperstringOptional wrapper key for data

With Wrapper

Use the wrapper option to structure data with metadata:

js
invoices: sails.inertia.scroll(() => invoices, {
  page,
  perPage,
  total,
  wrapper: 'data'
})

This produces:

js
{
  invoices: {
    data: [...],  // The invoice items
    meta: {
      page: 0,
      perPage: 20,
      total: 150,
      totalPages: 8,
      hasMore: true
    }
  }
}

Complete Example

Backend

js
// api/controllers/invoice/view-invoices.js
module.exports = {
  inputs: {
    page: {
      type: 'number',
      defaultsTo: 0
    },
    status: {
      type: 'string',
      defaultsTo: 'sent'
    }
  },

  exits: {
    success: { responseType: 'inertia' }
  },

  fn: async function ({ page, status }) {
    const perPage = 20
    const currentCreatorId = this.req.session.creatorId

    const invoices = await Invoice.find({
      creator: currentCreatorId,
      status
    })
      .populate('client')
      .sort('updatedAt DESC')
      .paginate(page, perPage)

    const total = await Invoice.count({
      creator: currentCreatorId,
      status
    })

    return {
      page: 'invoices/index',
      props: {
        status,
        invoices: sails.inertia.scroll(() => invoices, {
          page,
          perPage,
          total,
          wrapper: 'data'
        })
      }
    }
  }
}

Frontend (Vue)

vue
<script setup>
import { router } from '@inertiajs/vue3'
import { ref, computed } from 'vue'

const props = defineProps({
  invoices: Object,
  status: String
})

const loading = ref(false)

const hasMore = computed(() => props.invoices.meta.hasMore)

function loadMore() {
  if (loading.value || !hasMore.value) return

  loading.value = true
  router.reload({
    data: {
      page: props.invoices.meta.page + 1
    },
    preserveState: true,
    preserveScroll: true,
    only: ['invoices'],
    onFinish: () => {
      loading.value = false
    }
  })
}
</script>

<template>
  <div>
    <div v-for="invoice in invoices.data" :key="invoice.id">
      {{ invoice.invoiceNumber }} - {{ invoice.totalAmount }}
    </div>

    <button v-if="hasMore" @click="loadMore" :disabled="loading">
      {{ loading ? 'Loading...' : 'Load More' }}
    </button>

    <p v-if="!hasMore">No more invoices</p>
  </div>
</template>

Frontend (React)

jsx
import { router } from '@inertiajs/react'
import { useState } from 'react'

export default function Invoices({ invoices, status }) {
  const [loading, setLoading] = useState(false)

  function loadMore() {
    if (loading || !invoices.meta.hasMore) return

    setLoading(true)
    router.reload({
      data: { page: invoices.meta.page + 1 },
      preserveState: true,
      preserveScroll: true,
      only: ['invoices'],
      onFinish: () => setLoading(false)
    })
  }

  return (
    <div>
      {invoices.data.map((invoice) => (
        <div key={invoice.id}>
          {invoice.invoiceNumber} - {invoice.totalAmount}
        </div>
      ))}

      {invoices.meta.hasMore && (
        <button onClick={loadMore} disabled={loading}>
          {loading ? 'Loading...' : 'Load More'}
        </button>
      )}
    </div>
  )
}

Scroll vs Merge

Featurescroll()merge()
Pagination metadataYesNo
Auto-merge behaviorYesYes
Wrapper supportYesNo
Use caseFull paginationSimple append

Use scroll() when you need pagination info (page count, has more). Use merge() for simple append scenarios.

All open source projects are released under the MIT License.