Displaying data in lists is a core feature of most applications. Ginjou provides useList and useInfiniteList to make this easy.
useList is the go-to composable for fetching lists of data.
Composition:
currentPage, perPage, filters, and sorters.useGetList to fetch data based on the current state.useGo and useLocation to sync state with the URL (if enabled).<script setup lang="ts">
import { useList } from '@ginjou/vue'
const { records, isFetching } = useList({
resource: 'posts',
})
</script>
<template>
<div v-if="isFetching">
Loading...
</div>
<ul v-else>
<li v-for="record in records" :key="record.id">
{{ record.id }} - {{ record.title }}
</li>
</ul>
</template>
<!-- WIP -->
<script>
// ...
</script>
For "Load More" or Infinite Scroll interfaces, use useInfiniteList.
Composition:
perPage (or limit), filters, and sorters. Note it does NOT use standard page-based pagination state like useList in the same way.useGetInfiniteList.<script setup lang="ts">
import { useInfiniteList } from '@ginjou/vue'
const {
records, // Note: This is a nested array of pages -> records
hasNextPage,
fetchNextPage,
isFetching,
} = useInfiniteList({
resource: 'posts',
pagination: {
perPage: 10,
},
})
</script>
<template>
<div v-for="(page, i) in records" :key="i">
<div v-for="item in page" :key="item.id">
{{ item.title }}
</div>
</div>
<button
v-if="hasNextPage"
:disabled="isFetching"
@click="fetchNextPage()"
>
{{ isFetching ? 'Loading...' : 'Load More' }}
</button>
</template>
<!-- WIP -->
<script>
// ...
</script>
useList provides currentPage, perPage, and pageCount refs.
You can control where pagination happens using pagination.mode:
'server' (Default): Parameters are sent to the API.'client': All data is expected to be available (or fetched once), and Ginjou slices the array in the browser.'off': Pagination is disabled.<script setup lang="ts">
import { useList } from '@ginjou/vue'
const {
records,
currentPage,
perPage,
pageCount,
total,
} = useList({
resource: 'posts',
pagination: {
current: 1,
perPage: 10,
mode: 'server', // or 'client', 'off'
}
})
</script>
<template>
<!-- List rendering... -->
<div class="pagination">
<button :disabled="currentPage === 1" @click="currentPage--">
Prev
</button>
<span>{{ currentPage }} / {{ pageCount }}</span>
<button :disabled="currentPage === pageCount" @click="currentPage++">
Next
</button>
</div>
</template>
<!-- WIP -->
<script>
// ...
</script>
Updates to the filters array trigger data refetches (or client-side filtering).
Controlled by filters.mode:
'server' (Default): Filters are sent to the API.'off': Filters are ignored/disabled.(Note: Client-side filtering logic for useList is typically handled by the developer or specific helpers if mode: 'client' logic is needed, but the primary supported modes for the prop are server/off for API interaction)
<script setup lang="ts">
import { FilterOperator } from '@ginjou/core'
import { useList } from '@ginjou/vue'
import { reactive, unref, watch } from 'vue'
const { records, filters } = useList({
resource: 'posts',
filters: {
mode: 'server',
}
})
// ... form logic to update filters ...
</script>
<!-- WIP -->
<script>
// ...
</script>
Modify sorters to change order.
Controlled by sorters.mode:
'server' (Default): Sort params sent to API.'off': Sorting disabled.<script setup lang="ts">
import { useList } from '@ginjou/vue'
const { records, sorters } = useList({
resource: 'posts',
sorters: {
mode: 'server',
value: [
{ field: 'created_at', order: 'desc' },
],
}
})
</script>
<!-- WIP -->
<script>
// ...
</script>
syncRoute keeps your state in sync with the URL.
syncRoute is highly recommended for list pages, as it allows users to bookmark or share specific search results and pagination states.<script setup lang="ts">
const { records } = useList({
resource: 'posts',
syncRoute: true,
})
// URL becomes: ?current=1&perPage=10...
</script>
<!-- WIP -->
<script>
// ...
</script>
You can configure each part individually.
Disable Pagination Sync:
useList({
syncRoute: {
currentPage: false,
perPage: false,
}
})
Custom Filter Sync:
useList({
syncRoute: {
filters: {
field: 'q', // use ?q=... instead of ?filters=...
// You can also provide custom parse/stringify functions
}
}
})
Use permanent to enforce constraints that users cannot remove.
<script setup lang="ts">
import { FilterOperator } from '@ginjou/core'
import { useList } from '@ginjou/vue'
const { records } = useList({
resource: 'posts',
filters: {
permanent: [
{
field: 'status',
operator: FilterOperator.eq,
value: 'published',
}
],
value: []
}
})
</script>
<!-- WIP -->
<script>
// ...
</script>